私は昔のMySQLユーザーで、サブクエリよりもJOIN
を優先していました。しかし今日では誰もがサブクエリを使っています、そして私はそれが嫌いです。理由はわかりません。
違いがあるかどうか自分で判断するための理論的知識が不足しています。サブクエリはJOIN
と同程度に優れているので、心配することは何もありませんか?
MySQLマニュアルからの抜粋 ( 13.2.10.11結合へのサブクエリの書き換え ):
サーバが最適化できる可能性があるため、LEFT [OUTER] JOINは同等のサブクエリよりも高速になる場合があります。これはMySQLサーバだけに固有のものではありません。
そのため、サブクエリはLEFT [OUTER] JOIN
より遅くなる可能性がありますが、私の意見ではそれらの強さは読みやすさがやや高いです。
サブクエリは、「Aから事実を取得し、Bからの事実を条件とする」という形式の問題を解決するための論理的に正しい方法です。そのような場合、結合を行うよりも副問い合わせにBを付けるほうが論理的に意味があります。 Bに対する複数の一致により、Aから重複する事実を取得することに注意する必要はないため、実用的な意味でも安全です。
しかし、実際には、答えは通常パフォーマンスにかかっています。一部のオプティマイザは、ジョインとサブクエリを与えられたときにレモンを吸い、他の方法ではレモンを吸います。これはオプティマイザ固有、DBMSバージョン固有、およびクエリ固有です。
歴史的には、明示的結合は通常勝つので、結合はより優れているという確立された知恵がありますが、オプティマイザーは常により良くなっています。
ほとんどの場合、JOIN
sはサブクエリよりも速く、サブクエリのほうが速いことは非常にまれです。
JOIN
sでは、すべてのクエリを実行してすべてのデータをロードして処理するサブクエリとは異なり、RDBMSはクエリに適した実行計画を作成し、処理するためにロードするデータを予測して時間を節約できます。 。
サブクエリの良いところは、それらがJOIN
sより読みやすいということです。それが、ほとんどの新しいSQLの人々がそれらを好む理由です。それは簡単な方法です。パフォーマンスに関しては、JOINSはほとんどの場合、読みにくくはありませんが優れています。
データベースがデータに対するクエリをどのように実行するかを確認するには、EXPLAINを使用します。この答えには巨大な「依存している」ことがあります。
PostgreSQLは、一方が他方より速いと考えるとき、副問い合わせを結合または副問い合わせへの結合に書き換えることができます。それはすべてデータ、インデックス、相関、データ量、クエリなどに依存します。
まず、最初の2つを比較するには、クエリとサブクエリを区別してください。
クエリの最初のクラス の場合、優れたRDBMSは結合とサブクエリを同等のものと見なし、同じクエリプランを生成します。
最近ではmysqlでもそれが可能です。
それでも、時にはそうではありませんが、これは結合が常に勝つという意味ではありません - mysqlでサブクエリを使用するとパフォーマンスが向上する場合があります。 (例えば、mysqlプランナーが正しくコストを見積もることを妨げる何かがあり、プランナーがjoin-variantとsubquery-variantが同じではないと判断した場合、サブクエリは特定のパスを強制することで結合を上回る可能性があります)。
結論として、どちらがより良く機能するかを確かめたい場合は、joinとsubqueryの両方のバリエーションについてクエリをテストする必要があります。
2番目のクラス の場合、結合を使用してこれらのクエリを書き換えることはできないため、比較は意味がありません。この場合、サブクエリは必要なタスクを実行するための自然な方法です。
2010年には、私はこの質問の作者に加わり、JOIN
に強く投票したでしょう。しかし、はるかに多くの経験(特にMySQL)を使えば、次のように述べることができます。はいサブクエリの方が優れている可能性があります。私はここで複数の答えを読みました。サブクエリのほうが速いと言う人もいますが、説明が足りません。私はこの(非常に)遅い答えを提供してくれることを願っています:
まず最初に、最も重要なことを述べておきましょう。 サブクエリにはさまざまな形式があります
そして2番目に重要なことは: サイズが重要
サブクエリを使用する場合は、DBサーバがサブクエリをどのように実行するのか 注意してください が必要です。特に 副問い合わせが1回または1行ごとに評価される場合 反対に、最近のDB-Serverは多くのことを最適化することができます。場合によっては、サブクエリがクエリの最適化に役立ちますが、新しいバージョンのDB-Serverでは最適化が廃止される可能性があります。
SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo
サブクエリはfoo
から得られたすべての行に対して実行されることに注意してください。可能であればこれを避けてください、それは巨大なデータセットであなたの問い合わせを劇的に遅くするかもしれません。しかし、サブクエリがfoo
を参照していない場合は、DBサーバによって静的コンテンツとして最適化され、一度だけ評価される可能性があります。
SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)
運がよければ、DBはこれを内部的にJOIN
に最適化します。そうでないと、select-typeのような結果だけではなく、foo
内のすべての行に対してサブクエリを実行するため、巨大なデータセットではクエリは非常に遅くなります。
SELECT moo, bar
FROM foo
LEFT JOIN (
SELECT MIN(bar), me FROM wilco GROUP BY me
) ON moo = me
これは面白い。 JOIN
をサブクエリと組み合わせます。そして、ここで私たちはサブクエリの本当の強みを手に入れます。 wilco
に何百万もの行が含まれているが、少数の異なるme
が含まれているデータセットを想像してください。巨大なテーブルに対してジョインする代わりに、今度は小さいテンポラリテーブルに対してジョインする必要があります。データベースのサイズによっては、これによりクエリがはるかに高速になる可能性があります。 CREATE TEMPORARY TABLE ...
とINSERT INTO ... SELECT ...
を使用しても同じ効果が得られます。これにより、非常に複雑なクエリで読みやすくなります(ただし、繰り返し可能な読み取り分離レベルでデータセットをロックできます)。
SELECT moo, bar
FROM (
SELECT moo, CONCAT(roger, wilco) AS bar
FROM foo
GROUP BY moo
HAVING bar LIKE 'SpaceQ%'
) AS temp_foo
GROUP BY bar
ORDER BY bar
サブクエリは複数のレベルでネストできます。結果をグループ化またはソートする必要がある場合、これは巨大なデータセットに役立ちます。通常、DB-Serverはこのために一時テーブルを作成しますが、テーブル全体をソートする必要がなく、結果セットのみをソートする必要がある場合もあります。テーブルのサイズによっては、これによってパフォーマンスが大幅に向上する可能性があります。
サブクエリはJOIN
に代わるものではありませんので、(可能ではありますが)このように使うべきではありません。私の控え目な意見では、サブクエリの正しい使い方はCREATE TEMPORARY TABLE ...
の簡単な置き換えとしての使い方です。良い副問い合わせはある意味でデータセットを減らします、ON
のJOIN
ステートメントでは達成できません。サブクエリがキーワードGROUP BY
またはDISTINCT
のいずれかを持ち、好ましくはselectフィールドまたはwhereステートメントに配置されていない場合は、パフォーマンスが大幅に向上する可能性があります。
サブクエリを含む多くのTransact-SQLステートメントは、代わりに結合として定式化できます。他の質問はサブクエリでのみ提起できます。 Transact-SQLでは、通常、副問い合わせを含む文と含まれていない意味的に同等の文との間にパフォーマンス上の違いはありません。ただし、存在を確認する必要がある場合は、結合の方がパフォーマンスが向上します。それ以外の場合は、重複を確実に排除するために、外側のクエリの結果ごとにネストされたクエリを処理する必要があります。そのような場合は、結合アプローチを使用するとより良い結果が得られます。
あなたがのような何かを必要とすればそう
select * from t1 where exists select * from t2 where t2.parent=t1.id
代わりにjoinを使ってみてください。それ以外の場合は違いはありません。
私は言う:サブクエリのために functions を作成することは雑然とした問題を取り除き、サブクエリに追加のロジックを実装することを可能にする。そのため、可能な限り副問い合わせ用の関数を作成することをお勧めします。
コードの乱雑さは大きな問題であり、業界は何十年もの間それを回避することに取り組んできました。
引用された回答で強調されていないのは、duplicatesの問題と、特定の(使用)ケースから生じる可能性のある問題のある結果だと思います。
(マルセロ・カントスはそれについて言及していますが)
スタンフォード大学のSQLに関するLagunitaコースの例を引用します。
+------+--------+------+--------+
| sID | sName | GPA | sizeHS |
+------+--------+------+--------+
| 123 | Amy | 3.9 | 1000 |
| 234 | Bob | 3.6 | 1500 |
| 345 | Craig | 3.5 | 500 |
| 456 | Doris | 3.9 | 1000 |
| 567 | Edward | 2.9 | 2000 |
| 678 | Fay | 3.8 | 200 |
| 789 | Gary | 3.4 | 800 |
| 987 | Helen | 3.7 | 800 |
| 876 | Irene | 3.9 | 400 |
| 765 | Jay | 2.9 | 1500 |
| 654 | Amy | 3.9 | 1000 |
| 543 | Craig | 3.4 | 2000 |
+------+--------+------+--------+
(特定の大学および専攻への申請)
+------+----------+----------------+----------+
| sID | cName | major | decision |
+------+----------+----------------+----------+
| 123 | Stanford | CS | Y |
| 123 | Stanford | EE | N |
| 123 | Berkeley | CS | Y |
| 123 | Cornell | EE | Y |
| 234 | Berkeley | biology | N |
| 345 | MIT | bioengineering | Y |
| 345 | Cornell | bioengineering | N |
| 345 | Cornell | CS | Y |
| 345 | Cornell | EE | N |
| 678 | Stanford | history | Y |
| 987 | Stanford | CS | Y |
| 987 | Berkeley | CS | Y |
| 876 | Stanford | CS | N |
| 876 | MIT | biology | Y |
| 876 | MIT | marine biology | N |
| 765 | Stanford | history | Y |
| 765 | Cornell | history | N |
| 765 | Cornell | psychology | Y |
| 543 | MIT | CS | N |
+------+----------+----------------+----------+
CS
major(大学に関係なく)に応募した学生のGPAスコアを見つけてみましょう
サブクエリの使用:
select GPA from Student where sID in (select sID from Apply where major = 'CS');
+------+
| GPA |
+------+
| 3.9 |
| 3.5 |
| 3.7 |
| 3.9 |
| 3.4 |
+------+
この結果セットの平均値は次のとおりです。
select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');
+--------------------+
| avg(GPA) |
+--------------------+
| 3.6800000000000006 |
+--------------------+
結合の使用:
select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';
+------+
| GPA |
+------+
| 3.9 |
| 3.9 |
| 3.5 |
| 3.7 |
| 3.7 |
| 3.9 |
| 3.4 |
+------+
この結果セットの平均値:
select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';
+-------------------+
| avg(GPA) |
+-------------------+
| 3.714285714285714 |
+-------------------+
平均値の計算のために重複をカウントすることを考えると、2回目の試行がユースケースで誤解を招く結果をもたらすことは明らかです。また、結合ベースのステートメントでdistinct
を使用すると、3.9
スコアの3つのオカレンスのうち1つが誤って保持されることを考えると、問題がnotで除去されることも明らかです。 。正しいケースは、TWO(2)3.9
スコアの出現を考慮に入れることです。実際にTWO(2)を満たしている生徒は、クエリ基準。
場合によっては、パフォーマンスの問題に加えて、サブクエリが最も安全な方法であると思われます。
古いMambo CMSの非常に大きなデータベースで実行します。
SELECT id, alias
FROM
mos_categories
WHERE
id IN (
SELECT
DISTINCT catid
FROM mos_content
);
0秒
SELECT
DISTINCT mos_content.catid,
mos_categories.alias
FROM
mos_content, mos_categories
WHERE
mos_content.catid = mos_categories.id;
〜3秒
EXPLAINは、正確に同じ行数を調べていることを示していますが、1つは3秒かかり、もう1つはほぼ瞬時です。この話の教訓?パフォーマンスが重要な場合(そうではないですか)、複数の方法で試して、どれが最も速いかを確認してください。
そして...
SELECT
DISTINCT mos_categories.id,
mos_categories.alias
FROM
mos_content, mos_categories
WHERE
mos_content.catid = mos_categories.id;
0秒
繰り返しますが、同じ結果、同じ行数を調べました。私の推測では、DISTINCT mos_content.catidはDISTINCT mos_categories.idよりも理解するのにはるかに長い時間がかかります。
副問合せは通常、単一行をアトミック値として返すために使用されますが、INキーワードを使用して複数の行と値を比較するために使用されることもあります。ターゲットリスト、WHERE句など、SQLステートメント内のほとんどすべての意味のある場所で使用できます。単純なサブクエリを検索条件として使用できます。たとえば、一対のテーブル間では、
SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');
サブクエリの結果に通常の値演算子を使用するには、1つのフィールドのみを返す必要があることに注意してください。他の値のセット内に単一の値が存在するかどうかを調べたい場合は、INを使用してください。
SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');
これは、結合条件でテーブルBの一致するレコードが見つからない場合でも、テーブルAとBの要素を結合するだけのLEFT-JOINとは明らかに異なります。
速度が気になる場合は、データベースを調べて適切なクエリを作成し、パフォーマンスに大きな違いがないかどうかを確認する必要があります。
2つのケースのような私の観察によると、テーブルのレコード数が100,000未満の場合、結合は速く機能します。
しかし、テーブルに100,000を超えるレコードがある場合は、副問合せが最適です。
クエリの下に作成した500,000レコードを含むテーブルが1つあり、その結果の時間は次のようになります。
SELECT *
FROM crv.workorder_details wd
inner join crv.workorder wr on wr.workorder_id = wd.workorder_id;
結果:13.3秒
select *
from crv.workorder_details
where workorder_id in (select workorder_id from crv.workorder)
結果:1.65秒
サブクエリはその場で集約関数を計算することができます。例えば。本の最低価格を見つけて、この価格で販売されているすべての本を入手してください。 1)副問い合わせを使用する
SELECT titles, price
FROM Books, Orders
WHERE price =
(SELECT MIN(price)
FROM Orders) AND (Books.ID=Orders.ID);
2)JOINを使う
SELECT MIN(price)
FROM Orders;
-----------------
2.99
SELECT titles, price
FROM Books b
INNER JOIN Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;
MySQLのバージョン:5.5.28-0ubuntu0.12.04.2-log
また、JOINは常にMySQLのサブクエリよりも優れているという印象を受けましたが、EXPLAINが判断を下すためのより良い方法です。これは、サブクエリがJOINよりもうまく機能する例です。
これが3つのサブクエリを持つ私のクエリです。
EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date
FROM `vote-ranked-listory` vrl
INNER JOIN lists l ON l.list_id = vrl.list_id
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION'
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000
AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL
AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL
AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL
ORDER BY vrl.moved_date DESC LIMIT 200;
説明:
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| 1 | PRIMARY | vrl | index | PRIMARY | moved_date | 8 | NULL | 200 | Using where |
| 1 | PRIMARY | l | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY | 4 | ranker.vrl.list_id | 1 | Using where |
| 1 | PRIMARY | vrlih | eq_ref | PRIMARY | PRIMARY | 9 | ranker.vrl.list_id,ranker.vrl.ontology_id,const | 1 | Using where |
| 1 | PRIMARY | lbs | eq_ref | PRIMARY,idx_list_burial_state,burial_score | PRIMARY | 4 | ranker.vrl.list_id | 1 | Using where |
| 4 | DEPENDENT SUBQUERY | list_tag | ref | list_tag_key,list_id,tag_id | list_tag_key | 9 | ranker.l.list_id,const | 1 | Using where; Using index |
| 3 | DEPENDENT SUBQUERY | list_tag | ref | list_tag_key,list_id,tag_id | list_tag_key | 9 | ranker.l.list_id,const | 1 | Using where; Using index |
| 2 | DEPENDENT SUBQUERY | list_tag | ref | list_tag_key,list_id,tag_id | list_tag_key | 9 | ranker.l.list_id,const | 1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
JOINと同じクエリは次のとおりです。
EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date
FROM `vote-ranked-listory` vrl
INNER JOIN lists l ON l.list_id = vrl.list_id
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION'
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL
ORDER BY vrl.moved_date DESC LIMIT 200;
そして出力は次のとおりです。
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| 1 | SIMPLE | lt3 | ref | list_tag_key,list_id,tag_id | tag_id | 5 | const | 2386 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | l | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY | 4 | ranker.lt3.list_id | 1 | Using where |
| 1 | SIMPLE | vrlih | ref | PRIMARY | PRIMARY | 4 | ranker.lt3.list_id | 103 | Using where |
| 1 | SIMPLE | vrl | ref | PRIMARY | PRIMARY | 8 | ranker.lt3.list_id,ranker.vrlih.ontology_id | 65 | Using where |
| 1 | SIMPLE | lt1 | ref | list_tag_key,list_id,tag_id | list_tag_key | 9 | ranker.lt3.list_id,const | 1 | Using where; Using index; Not exists |
| 1 | SIMPLE | lbs | eq_ref | PRIMARY,idx_list_burial_state,burial_score | PRIMARY | 4 | ranker.vrl.list_id | 1 | Using where |
| 1 | SIMPLE | lt2 | ref | list_tag_key,list_id,tag_id | list_tag_key | 9 | ranker.lt3.list_id,const | 1 | Using where; Using index |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
rows
列を比較すると違いがわかり、JOINを使用したクエリではUsing temporary; Using filesort
が使用されています。
もちろん、両方のクエリを実行すると、最初のクエリは0.02秒以内に実行され、2番目のクエリは1分後にも完了しないため、これらのクエリについてはEXPLAINが適切に説明しています。
list_tag
テーブルにINNER JOINがない場合、つまり削除した場合
AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL
最初のクエリから、それに対応して:
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403
2番目のクエリから、EXPLAINは両方のクエリに対して同じ行数を返し、これら両方のクエリは同じ速度で実行されます。
「あるRDBMSは、あるものが他のものより速いと考えるとき、subqueryをjoinまたはjoinからsubquery)に書き換えることができます)」と言う人もいますが、単純な場合に適用されますが、subqueries)を使用した複雑な照会では実際にはパフォーマンスに問題が生じません。
この違いは、2番目の結合テーブルに主テーブルよりもはるかに多くのデータがある場合にのみ見られます。私は以下のような経験をしました...
私たちは10万のエントリーと約30万のエントリーの彼らの会員データ(友情)のusersテーブルを持っていました。友人とそのデータを受け取るためのjoinステートメントでしたが、かなり遅れました。しかし、メンバーシップテーブルに少量のデータしかないところではうまく機能していました。サブクエリを使用するように変更すると、うまくいきました。
しかし、その間に結合クエリは、プライマリテーブルよりもエントリが少ない他のテーブルと連携しています。
だから私はjoinとsub queryステートメントはうまく機能していると思います、そしてそれはデータと状況に依存します。
最近では、多くのDBがサブクエリとジョインを最適化できます。したがって、Explainを使用してクエリーを調べ、どれが速いかを確認するだけで済みます。パフォーマンスにそれほど違いがない場合は、サブクエリを使用することをお勧めします。サブクエリは単純で理解しやすいためです。
私は同じ問題について考えているだけですが、FROMの部分で副照会を使用しています。大きなテーブルから接続してクエリする必要があります。「スレーブ」テーブルには2800万件のレコードがありますが、結果は128という非常に小さい結果のビッグデータになります。私はそれでMAX()関数を使っています。
最初はLEFT JOINを使用していますが、これは正しい方法だと思うので、mysqlは最適化できます。テストのために2回目は、JOINに対してsub-selectを書き換えます。
LEFT JOINランタイム:1.12秒SUB-SELECTランタイム:0.06秒
副選択は結合よりも18倍高速です。ちょっとちょっとADVに。副選択はひどいように見えますが、結果は...