這是一個非常基本的查詢我想不通....在不同行上選擇符合不同條件的值?
比方說,我有一個兩列的表是這樣的:
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