2009-01-25 42 views
29

这是一个非常基本的查询我想不通....在不同行上选择符合不同条件的值?

比方说,我有一个两列的表是这样的:

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

我想有roleids所有不同用户标识1,2和3.使用上面的例子,我想返回的唯一结果是userid 1.我该怎么做?

+1

广泛的细节添加到我的答案。 – cletus 2009-01-26 00:32:16

+3

任何提示像@clettus @@@应答的问题值得+1 +1 – cori 2011-12-08 19:28:19

回答

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

为了任何阅读:我的答案是简单明了的,并得到了“接受”的地位,但请不要再去读通过@cletus给出的answer。它有更好的表现。


Justing想大声,另一种方式来写自参加由@cletus描述是:

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的堆栈溢出7月数据转储)上运行此查询,并在1.08秒内返回结果。在分配足够内存给innodb_buffer_pool_size的体面服务器上,它应该更快。

109

好吧,我得到downvoted这个,所以我决定测试一下:

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. 

这增加了50万用户随意角色组合,大约有25,000个符合所选标准。

首先查询:

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

这是正确的。我提出的加入版本是,比汇总版本快20倍。

对不起,我这样做是为了生活,在现实世界和现实世界中工作,我们测试SQL,结果可以说明一切。

原因应该很清楚。聚合查询将按照表的大小进行成本缩放。每行都通过HAVING条款进行处理,汇总和过滤(或不)。连接版本将(使用索引)根据给定角色选择用户的子集,然后根据第二个角色检查该子集,最后针对第三个角色检查该子集。每个selection(在relational algebra条款)工作在一个越来越小的子集。从这里你可以得出结论:

连接版本的性能变得更好,匹配率更低。

如果只有500个用户(500k以上的示例中)有三个角色,那么连接版本将显着加快。汇总版本不会(并且任何性能改进都是因为传输500个用户而不是25k,连接版本显然也会得到)。

我也很好奇,看看真正的数据库(如Oracle)如何处理这个问题。所以我基本上重复了在Oracle XE上的相同练习(与上一个示例中的MySQL相同的Windows XP桌面机器上运行),结果几乎完全相同。

连接似乎不被接受,但正如我已经证明的那样,聚合查询可能会慢一个数量级。

更新:一些extensive testing后,画面更加复杂,答案将取决于你的数据,你的数据库和其他因素。故事的寓意是测试,测试和测试。

+5

对downvote没有评论?这实际上起作用。 – cletus 2009-01-25 01:18:00

+0

这个dv不是从我这里来的......但是认真的......你会把它放在你的系统中吗? – 2009-01-25 01:19:47

+1

我也没有dv它,但我会用这个,如果我绝对必须......我应该重新设计我的数据库,所以我不必这样的查询? – John 2009-01-25 01:26:23

-5

如果您在这里需要任何一种通用性(不同的3角色组合或不同的n角色组合)......我建议您为您的角色使用位掩码系统,并使用位运算符来执行您的查询...

3

假设用户ID,角色ID被包含在唯一索引(意味着不可能有2条记录,其中用户ID = x和角色ID = 1

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

经典方式做到这一点是把它当作一个关系除法问题

英文:选择那些对他们来说没有任何希望的角色ID值丢失用户

我假设你有其中的UserRole表是指一个用户表,我会承担所需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); 

我也会假设所有相关的列都不是NULLable,所以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 
) 
); 

这可能会或可能不会最终被高效,视指标,平台,数据等。在网上搜索“关系部门”,你会发现很多。

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

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