这是一个我无法弄清楚的非常基本的查询....
假设我有一个像这样的两列表:
userid | roleid
--------|--------
1 | 1
1 | 2
1 | 3
2 | 1
我希望获得所有具有roleids
1,2和3的不同用户ID。使用上面的示例,我想要返回的唯一结果是userid
1.我该怎么做?
答案 0 :(得分:113)
好的,我对此进行了投票,所以我决定对它进行测试:
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.
增加了500,000个随机用户角色组合,大约有25,000个符合所选标准。
首先查询:
SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3
查询时间:0.312秒
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.016秒
没错。我提议的加入版本比聚合版本快20倍。
很抱歉,我这样做是为了生活和工作在现实世界中,在现实世界中,我们测试SQL,结果不言而喻。
原因应该非常明确。聚合查询将根据表的大小按比例缩放。通过HAVING
子句处理,聚合和过滤(或不过滤)每一行。连接版本将(使用索引)根据给定角色选择用户的子集,然后针对第二个角色检查该子集,最后针对第三个角色检查该子集。每个selection(relational algebra个术语)都适用于越来越小的子集。由此可以得出结论:
匹配发生率较低时,加入版本的效果会更好。
如果只有500个用户(在上面的500k样本中)具有三个声明的角色,则加入版本将显着加快。聚合版本不会(并且任何性能改进都是由于传输500个用户而不是25k用户,加入版本显然也是如此)。
我也很想知道真正的数据库(即Oracle)如何处理这个问题。所以我基本上在Oracle XE上重复了相同的练习(在与前一个例子中的MySQL相同的Windows XP台式机上运行),结果几乎相同。
加入似乎不受欢迎,但正如我所证明的那样,聚合查询可能会慢一个数量级。
更新:在某些extensive testing之后,图片会更复杂,答案取决于您的数据,数据库和其他因素。故事的寓意是测试,测试,测试。
答案 1 :(得分:25)
SELECT userid
FROM UserRole
WHERE roleid IN (1, 2, 3)
GROUP BY userid
HAVING COUNT(DISTINCT roleid) = 3;
对于阅读此内容的任何人:我的回答简单明了,并且获得了“已接受”状态,但请继续阅读@cletus提供的answer。它有更好的表现。
只是大声思考,编写@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万行(PostTags的Stack Overflow July数据转储)的查询,并在1.08秒内返回结果。在具有足够内存分配给innodb_buffer_pool_size
的体面服务器上,它应该更快。
答案 2 :(得分:4)
这样做的经典方法是将其视为关系划分问题。
英文:选择缺少所需角色值的用户。
我假设你有一个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
)
);
根据索引,平台,数据等,这可能会也可能不会有效率。在网络上搜索“关系部门”,你会发现很多。
答案 3 :(得分:3)
假设userid,roleid包含在唯一索引中(意味着不能有2条记录,其中userid = x和roleid = 1
select count(*), userid from t
where roleid in (1,2,3)
group by userid
having count(*) = 3
答案 4 :(得分:1)
select userid from userrole where userid = 1
intersect
select userid from userrole where userid = 2
intersect
select userid from userrole where userid = 3
这不会解决问题吗?在典型的关系数据库上,这个解决方案有多好?查询优化器会自动优化吗?
答案 5 :(得分:-6)
如果你需要任何一般性(不同的3个角色组合或不同的n个角色组合)......我建议你为你的角色使用一个掩码系统,并使用按位运算符来执行你的查询。 ..