这是一个非常基本的查询我想不通....在不同行上选择符合不同条件的值?
比方说,我有一个两列的表是这样的:
userid | roleid
--------|--------
1 | 1
1 | 2
1 | 3
2 | 1
我想有roleids
所有不同用户标识1,2和3.使用上面的例子,我想返回的唯一结果是userid
1.我该怎么做?
这是一个非常基本的查询我想不通....在不同行上选择符合不同条件的值?
比方说,我有一个两列的表是这样的:
userid | roleid
--------|--------
1 | 1
1 | 2
1 | 3
2 | 1
我想有roleids
所有不同用户标识1,2和3.使用上面的例子,我想返回的唯一结果是userid
1.我该怎么做?
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
的体面服务器上,它应该更快。
好吧,我得到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后,画面更加复杂,答案将取决于你的数据,你的数据库和其他因素。故事的寓意是测试,测试和测试。
如果您在这里需要任何一种通用性(不同的3角色组合或不同的n角色组合)......我建议您为您的角色使用位掩码系统,并使用位运算符来执行您的查询...
假设用户ID,角色ID被包含在唯一索引(意味着不可能有2条记录,其中用户ID = x和角色ID = 1
select count(*), userid from t
where roleid in (1,2,3)
group by userid
having count(*) = 3
经典方式做到这一点是把它当作一个关系除法问题
英文:选择那些对他们来说没有任何希望的角色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
)
);
这可能会或可能不会最终被高效,视指标,平台,数据等。在网上搜索“关系部门”,你会发现很多。
select userid from userrole where userid = 1
intersect
select userid from userrole where userid = 2
intersect
select userid from userrole where userid = 3
这不会解决问题吗?在典型的关系数据库上,这个解决方案有多好?查询优化器会自动优化这个吗?
广泛的细节添加到我的答案。 – cletus 2009-01-26 00:32:16
任何提示像@clettus @@@应答的问题值得+1 +1 – cori 2011-12-08 19:28:19