在不同的行上选择满足不同条件的值?

bvk5enib  于 2021-06-21  发布在  Mysql
关注(0)|答案(6)|浏览(369)

这是一个非常基本的问题,我搞不懂。。。。
假设我有一个这样的两列表:

userid  |  roleid
--------|--------
   1    |    1
   1    |    2
   1    |    3
   2    |    1

我想得到所有具有 roleids 1、2和3。使用上面的例子,我想要返回的唯一结果是 userid 1我该怎么做?

ki0zmccv

ki0zmccv1#

实现这一点的经典方法是将其视为关系划分问题。
简体中文:选择没有缺少所需roleid值的用户。
我假设您有一个userrole表所引用的users表,并且我假设所需的roleid值在一个表中:

create table RoleGroup(
  roleid int not null,
  primary key(roleid)
)
insert into RoleGroup values (1);
insert into RoleGroup values (2);
insert into RoleGroup values (3);

我还将假设所有相关列都不可为null,因此in或not exists没有什么意外。下面是一个sql查询,它表达了上面的英语:

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where not exists (
    select R.roleid from UserRole as R
    where R.roleid = G.roleid
    and R.userid = U.userid
  )
);

另一种写法是

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where G.roleid not in (
    select R.roleid from UserRole as R
    where R.userid = U.userid
  )
);

根据索引、平台、数据等的不同,这种方法最终可能有效,也可能无效。在web上搜索“关系划分”,你会发现很多。

kiz8lqtg

kiz8lqtg2#

SELECT userid
FROM UserRole
WHERE roleid IN (1, 2, 3)
GROUP BY userid
HAVING COUNT(DISTINCT roleid) = 3;

对于阅读本文的人:我的答案简单明了,获得了“接受”的状态,但请务必阅读@cletus给出的答案。它有更好的性能。
justing大声思考,另一种写@cletus描述的self-join的方法是:

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid
JOIN userrole t3 ON t2.userid = t3.userid
WHERE (t1.roleid, t2.roleid, t3.roleid) = (1, 2, 3);

这对您来说可能更容易阅读,mysql支持这样的元组比较。mysql还知道如何智能地利用覆盖索引进行查询。把它看一遍 EXPLAIN 请参阅所有三个表的注解中的“使用索引”,这意味着它正在读取索引,甚至不必接触数据行。
我在macbook上使用mysql 5.1.48对210万行(posttag的堆栈溢出数据转储)进行了查询,结果在1.08秒内返回。有足够的内存分配给 innodb_buffer_pool_size ,应该更快。

k5hmc34c

k5hmc34c3#

select userid from userrole where userid = 1
intersect
select userid from userrole where userid = 2
intersect
select userid from userrole where userid = 3

这不能解决问题吗?在典型的关系数据库上这是一个多好的解决方案?查询优化器会自动优化吗?

hof1towb

hof1towb4#

如果您需要任何类型的通用性(不同的3角色组合或不同的n角色组合)…我建议您为您的角色使用位屏蔽系统,并使用位运算符执行查询。。。

cidc1ykv

cidc1ykv5#

好吧,我被否决了,所以我决定测试一下:

CREATE TABLE userrole (
  userid INT,
  roleid INT,
  PRIMARY KEY (userid, roleid)
);

CREATE INDEX ON userrole (roleid);

运行以下命令:

<?php
ini_set('max_execution_time', 120); // takes over a minute to insert 500k+ records 

$start = microtime(true);

echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
    echo "Connect error: " . mysql_error() . "\n";
}
mysql_select_db('scratch');
if (mysql_error()) {
    echo "Selct DB error: " . mysql_error() . "\n";
}

$users = 200000;
$count = 0;
for ($i=1; $i<=$users; $i++) {
    $roles = rand(1, 4);
    $available = range(1, 5);
    for ($j=0; $j<$roles; $j++) {
        $extract = array_splice($available, rand(0, sizeof($available)-1), 1);
        $id = $extract[0];
        query("INSERT INTO userrole (userid, roleid) VALUES ($i, $id)");
        $count++;
    }
}

$stop = microtime(true);
$duration = $stop - $start;
$insert = $duration / $count;

echo "$count users added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $insert seconds.\n";
echo "</pre>\n";

function query($str) {
    mysql_query($str);
    if (mysql_error()) {
        echo "$str: " . mysql_error() . "\n";
    }
}
?>

输出:

499872 users added.
Program ran for 56.5513510704 seconds.
Insert time 0.000113131663847 seconds.

这增加了500000个随机用户角色组合,大约有25000个符合所选标准。
第一个查询:

SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3

查询时间:0.312s

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid AND t2.roleid = 2
JOIN userrole t3 ON t2.userid = t3.userid AND t3.roleid = 3
AND t1.roleid = 1

查询时间:0.016s
这是正确的。我提议的join版本比聚合版本快20倍。
抱歉,我这样做是为了在现实世界中生活和工作,在现实世界中我们测试sql,结果不言自明。
原因应该很清楚。聚合查询将根据表的大小按成本进行缩放。每一行都通过 HAVING 条款。join版本将(使用索引)根据给定的角色选择一个用户子集,然后对照第二个角色检查该子集,最后对照第三个角色检查该子集。每个选择(在关系代数术语中)都作用于越来越小的子集。由此可以得出结论:
join版本的性能随着匹配率的降低而变得更好。
如果只有500个用户(在上面的500k示例中)拥有这三个指定的角色,那么join版本将显著加快。聚合版本不会(任何性能改进都是传输500个用户的结果,而不是25k,join版本显然也会得到25k)。
我也很想知道一个真正的数据库(如oracle)会如何处理这个问题。因此,我基本上在oraclexe上重复了相同的练习(与上一个示例中的mysql在同一台windowsxp桌面机上运行),结果几乎相同。
连接似乎不受欢迎,但正如我所演示的,聚合查询可能会慢一个数量级。
更新:经过一些广泛的测试,图片更复杂,答案将取决于你的数据,你的数据库和其他因素。这个故事的寓意是考验,考验,考验。

gab6jxml

gab6jxml6#

假设userid,roleid包含在唯一索引中(这意味着userid=x和roleid=1不能有2条记录)

select count(*), userid from t
where roleid in (1,2,3)
group by userid
having count(*) = 3

相关问题