異なる行で異なる条件を満たす値を選択しますか?
-
20-08-2019 - |
質問
これは私が理解できない非常に基本的なクエリです...
次のような2列のテーブルがあるとします:
userid | roleid
--------|--------
1 | 1
1 | 2
1 | 3
2 | 1
roleids
1、2、および3を持つすべての個別のユーザーIDを取得したい。上記の例を使用すると、返される結果はuserid
1のみです。
解決
SELECT userid
FROM UserRole
WHERE roleid IN (1, 2, 3)
GROUP BY userid
HAVING COUNT(DISTINCT roleid) = 3;
これを読んでいる人へ:私の答えはシンプルで簡単で、「受け入れられた」ステータスになりましたが、 answer @cletusが指定します。パフォーマンスが大幅に向上しています。
大声で考えるだけで、@ 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
を実行して、<!> quot; Using index <!> quot;を参照してください。 3つのテーブルすべてのノートに記載されています。つまり、インデックスを読み取っており、データ行に触れる必要さえありません。
MacbookでMySQL 5.1.48を使用してこのクエリを210万行(PostTagsのStack Overflow Julyデータダンプ)で実行し、1.08秒で結果を返しました。 innodb_buffer_pool_size
に十分なメモリが割り当てられたまともなサーバーでは、さらに高速になります。
他のヒント
はい、私はこれに賛成票を投じたので、テストすることにしました:
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
句を介して処理、集計、およびフィルター処理されます(または処理されません)。結合バージョンは(インデックスを使用して)特定のロールに基づいてユーザーのサブセットを選択し、そのサブセットを2番目のロールに対してチェックし、最後にそのサブセットを3番目のロールに対してチェックします。各選択(リレーショナル代数用語)はますます小さなサブセットで機能します。これから、次のように結論付けることができます。
結合バージョンのパフォーマンスは、一致の発生率が低いほどさらに向上します。
上記の3つのロールを持つ500人のユーザー(上記の500,000サンプルのうち)のみがいる場合、結合バージョンは大幅に高速になります。集約バージョンはそうではありません(そして、パフォーマンスの改善は、25kの代わりに500ユーザーを転送した結果です。これは、結合バージョンも明らかになります)。
また、実際のデータベース(つまりOracle)がこれをどのように処理するかを知りたいと思いました。そのため、基本的にOracle XEで同じ演習を繰り返し(前の例のMySQLと同じWindows XPデスクトップマシンで実行)、結果はほぼ同じです。
結合は眉をひそめているように見えますが、私が実証したように、集計クエリは桁違いに遅くなる可能性があります。
更新: 広範なテスト、画像はより複雑で、答えはデータ、データベース、その他の要因に依存します。物語の教訓は、テスト、テスト、テストです。
これを行う古典的な方法は、それを関係部門の問題として扱うことです。
英語:目的の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
)
);
これは、インデックス、プラットフォーム、データなどに応じて、効率的である場合とそうでない場合があります。ウェブで<!> quot; relational division <!> quot;を検索します。たくさんあります。
useridを想定すると、roleidは一意のインデックスに含まれます(userid = xおよびroleid = 1である2つのレコードは存在できないことを意味します
select count(*), userid from t
where roleid in (1,2,3)
group by userid
having count(*) = 3
select userid from userrole where userid = 1
intersect
select userid from userrole where userid = 2
intersect
select userid from userrole where userid = 3
これで問題は解決しませんか?これは、典型的なリレーショナルDBでどの程度優れたソリューションですか?クエリオプティマイザーはこれを自動的に最適化しますか?
ここで何らかの一般性が必要な場合(異なる3ロールの組み合わせまたは異なるnロールの組み合わせ)...ロールにビットマスキングシステムを使用し、ビット演算子を使用してクエリを実行することをお勧めします。 ..