結合とサブクエリ


837

私は昔ながらのMySQLユーザーであり、常にサブクエリJOINよりも優先しています。しかし、今日では誰もがサブクエリを使用しており、私はそれが嫌いです。理由はわかりません。

何か違いがあるかどうかを自分で判断するための理論的な知識が不足しています。サブクエリはと同じくらい良いので、JOIN心配する必要はありませんか?


23
サブクエリはときどき素晴らしいです。彼らはMySQLでパフォーマンスの面で劣る。それらを使用しないでください。
runrig

8
サブクエリは暗黙的に特定のDBテクノロジで利用可能な結合として実行されるという印象を常に受け​​ていました。
Kezzer、

18
かなり大きなテーブルと結合する場合、サブクエリは常に問題があるわけではありません。好ましい方法は、その大きなテーブルからサブ選択を行い(行数を制限)、結合します。
ovais.tariq

136
「今日、誰もが
サブクエリを

3
潜在的に(はるかの特定が)関連:stackoverflow.com/questions/141278/subqueries-vs-joins/...
リーBrenecki

回答:


191

MySQLマニュアルから取得13.2.10.11結合としてのサブクエリの書き換え):

LEFT [OUTER] JOINは、サーバーがそれをより適切に最適化できる可能性があるため、同等のサブクエリよりも高速になる可能性があります。これは、MySQLサーバーのみに固有のことではありません。

したがって、サブクエリはよりも遅くなる可能性がありますLEFT [OUTER] JOINが、私の意見では、その長所は読みやすさがわずかに高いということです。


45
@ user1735921 IMO依存します...一般的に、コードの可読性は非常に重要です。それは、コードの後での管理にとって非常に重要であるためです...ドナルドクヌースの有名なステートメントを思い出してみましょう:「時期尚早な最適化がすべての根源ですプログラミングにおける悪(または少なくともそのほとんど)」。ただし、当然ながら、パフォーマンスが最も重要なプログラミング領域があります...理想的には、相互の調整に成功した場合:)
simhumileco

30
より複雑なクエリでは、結合はサブクエリよりもはるかに読みやすくなっています。サブクエリは私の頭の中の麺のボウルに変わります。
Zahra 2017年

6
@ user1735921確かに、特にクエリが非常に複雑になり、間違った処理を行って1日を費やして修正する場合は、いつものように、その間にバランスがあります。
fabio.sussetto 2017年

6
@ user1735921パフォーマンスの向上が将来的に必要なメンテナンス時間の増加に見合う価値がある場合のみ
Joshua Schlichting

3
私の意見Joinsub queryは構文が異なるので、比較できない読みやすさです。SQL構文に長けている限り、どちらも読みやすさが向上します。パフォーマンスはより重要です。
Thavaprakash Swaminathan

842

サブクエリは、「Aからファクトを取得し、Bからファクトを条件とする」という形式の問題を解決するための論理的に正しい方法です。このような場合、結合を行うよりも、Bをサブクエリに固定するほうが論理的です。また、Bに対して複数の一致があるため、Aから重複したファクトを取得することに注意する必要がないため、実際的な意味でも安全です。

しかし実際には、答えは通常パフォーマンスにあります。一部のオプティマイザは、結合とサブクエリが与えられたときにレモンを吸う場合もあれば、反対にレモンを吸う場合もあり、これはオプティマイザ固有、DBMSバージョン固有、およびクエリ固有です。

歴史的に、明示的な結合は通常勝つため、結合の確立された知恵はより優れていますが、オプティマイザは常に改善されているため、まず論理的に首尾一貫した方法でクエリを記述し、パフォーマンスの制約がこれを保証する場合は再構築することを好みます。


105
すばらしい答えです。また、開発者(特にアマチュアの開発者)は常にSQLに精通しているわけではないことも付け加えておきます。
アルバロ・ゴンサレス

4
+1この問題の論理的な説明を長い間探していましたが、これは私にとって論理的に思える唯一の答えです
Ali Umair

1
@Marcelo Cantos、「Bに対する複数の一致により、Aから重複したファクトを取得することについて注意する必要がないため、実際的な意味でも安全です」というステートメントの例を挙げていただけませんか?これは非常に洞察に満ちていますが、少し抽象的すぎると思いました。ありがとう。
Jinghui Niu

6
@JinghuiNiu高価なアイテムを購入したお客様:select custid from cust join bought using (custid) where price > 500。顧客が複数の高価なアイテムを購入した場合、ダブルアップが表示されます。この問題を解決するには、select custid from cust where exists (select * from bought where custid = cust.custid and price > 500)select distinct …代わりに使用することもできますが、多くの場合、オプティマイザまたはエバリュエーターのどちらかにより多くの作業が必要になります。
Marcelo Cantos

1
@MatTheWhaleええ、私は怠惰だった簡単な答えを使用しました。実際のシナリオでは、custidからcustidを超える列をプルします。
Marcelo Cantos 2017

357

ほとんどの場合、JOINsはサブクエリよりも高速であり、サブクエリが高速になることは非常にまれです。

JOINRDBMSは、クエリの方が良い実行計画を作成することができますし、それがすべてのクエリを実行し、処理を行うためにすべてのデータをロードしますサブクエリとは異なり、データが処理されるようにロードされるべきかを予測し、時間を節約することができます。

サブクエリの優れた点は、サブクエリがJOINs よりも読みやすいということです。そのため、ほとんどの新しいSQLの人々はそれらを好みます。それは簡単な方法です。ただし、パフォーマンスについては、JOINSの方が読みにくいものではありませんが、ほとんどの場合、JOINSの方が優れています。


14
はい、そのため、ほとんどのデータベースには、クエリを分析するときにサブクエリを結合に変換する最適化ステップとして含まれています。
Cine

16
この回答は、質問された質問に対して少し単純化されすぎています。あなたが述べるように:特定のサブクエリは問題ありませんし、特定のものはそうではありません。答えは実際には2つを区別するのに役立ちません。(また、「非常にまれ」は実際にはデータ/アプリに依存します)。
Unreason

21
ドキュメンテーション参照またはテスト結果であなたのポイントのいずれかを証明できますか?
UğurGümüşhan

62
特に100,000を超える行数に関しては、上位クエリへの後方参照を含むサブクエリで非常に良い経験をしました。事はメモリ使用量とスワップファイルへのページングのようです。結合は非常に大量のデータを生成するため、メモリに収まらない可能性があり、スワップファイルにページングする必要があります。これが当てはまる場合は常に、小さな副選択のクエリ時間はselect * from a where a.x = (select b.x form b where b.id = a.id)、結合と比較して非常に短いです。これは非常に具体的な問題ですが、場合によっては数時間から数分かかることがあります。
zuloo

13
私はOracleの経験があり、フィルタリングや並べ替えを行わない場合、サブクエリは大きなテーブルではるかに優れています。
アミールパシャザデー

130

EXPLAINを使用して、データベースがデータに対してクエリを実行する方法を確認します。この答えには「依存する」という巨大なものがあります...

PostgreSQLは、一方が他方よりも高速であると判断した場合、サブクエリを結合に、またはサブクエリへの結合を書き換えることができます。すべては、データ、インデックス、相関、データ量、クエリなどに依存します。


6
PostgreSQLはとても良いですし、有用なのは、それが目的が何であるかを理解し、優れているとPostgreSQLはそのデータを見る方法を知るには非常に良いもの、それは考えに基づいてクエリを修正する理由は、これは正確に
WojonsTech

ふww。大量のクエリを書き換える必要はないと思います!勝利のためのpostgresql。
Daniel Shin

77

2010年に私はこの質問の著者に加わって強く投票しましたJOINが、はるかに多くの経験(特にMySQLの場合)があれば、私は次のように述べることができます。ここで複数の回答を読みました。いくつかのサブクエリはより高速ですが、十分な説明がありませんでした。私はこの(非常に)遅い回答を提供できるといいのですが:

まず、最も重要なことを言いましょう:サブクエリにはさまざまな形式があります

2番目の重要なステートメント:サイズが重要

サブクエリを使用する場合は、DBサーバーがサブクエリを実行する方法に注意する必要があります。特に、サブクエリが1回またはすべての行に対して評価される場合は! 一方、最新のDBサーバーは多くの最適化を行うことができます。場合によっては、サブクエリがクエリの最適化に役立ちますが、新しいバージョンのDBサーバーは最適化を廃止する可能性があります。

選択フィールドのサブクエリ

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

からのすべての結果行に対してサブクエリが実行されることに注意してくださいfoo
可能であればこれを避けてください。巨大なデータセットでのクエリが大幅に遅くなる可能性があります。ただし、サブクエリが参照を持たない場合、fooDBサーバーは静的コンテンツとして最適化でき、一度しか評価できません。

Whereステートメントのサブクエリ

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

運が良ければ、DBはこれを内部的にに最適化しJOINます。そうでない場合、クエリはfoo、select-typeのような結果だけでなく、のすべての行に対してサブクエリを実行するため、巨大なデータセットでは非常に遅くなります。

結合ステートメントのサブクエリ

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
  ORDER BY bar

複数のレベルでサブクエリをネストできます。結果をグループ化または並べ替える必要がある場合、これは巨大なデータセットで役立ちます。通常、DB-Serverはこのために一時テーブルを作成しますが、場合によっては、結果セットのみでテーブル全体をソートする必要はありません。これにより、テーブルのサイズによってはパフォーマンスが大幅に向上する場合があります。

結論

サブクエリはaに代わるものではないため、JOINこのように使用することはできません(可能ですが)。私の控えめな意見では、サブクエリの正しい使い方はの素早い置き換えとしての使い方ですCREATE TEMPORARY TABLE ...。適切なサブクエリは、のONステートメントでは達成できない方法でデータセットを削減しますJOIN。サブクエリは、キーワードのいずれかを持っている場合GROUP BY、またはDISTINCT、好ましくは選択フィールドまたはステートメントに位置していない、それはパフォーマンスのAロットを向上することがあります。


3
の場合Sub-queries in the Join-statement:(1)サブクエリ自体から派生テーブルを生成するには、非常に長い時間がかかる場合があります。(2)結果の派生テーブルにはインデックスが付けられません。これら2つだけでは、SQLが大幅に遅くなる可能性があります。
jxc

@jxc私はMySQLについてのみ話すことができます(1)結合に似た一時テーブルがあります。時間はデータの量によって異なります。サブクエリでデータを削減できない場合は、結合を使用します。(2)これは正しい、一時テーブルのデータを削減できる要因によって異なります。実際のケースでは、結合サイズを数百万から数百に減らし、クエリ時間を数秒(フルインデックスを使用)からサブクエリで1/4秒に短縮することができました。
Trendfischer

IMO:(1)そのような一時テーブル(派生テーブル)はマテリアライズされないため、SQLを実行するたびに一時テーブルを再作成する必要があり、非常にコストがかかり、実際のボトルネックになる可能性があります(つまり、何百万ものグループを実行する) (2)10インデックスがないため、一時テーブルのサイズをレコードに縮小できる場合でも、他のテーブルを結合するときに、一時テーブルを使用しない場合よりも9倍多くのデータレコードをクエリする可能性があります。ところで、以前はdb(MySQL)でこの問題がありましたが、私の場合、サブクエリを使用するSELECT list方がはるかに高速でした。
jxc

@jxcサブクエリの使用があまり最適ではない例がたくさんあることは間違いありません。EXPLAIN最適化する前に、クエリで使用することをお勧めします。set profiling=1一時テーブルがボトルネックになっている場合は、古いものを簡単に見ることができます。また、インデックスでさえ処理時間が必要ですが、Bツリーはレコードのクエリを最適化しますが、10レコードのテーブルは、数百万のレコードのインデックスよりもはるかに高速です。しかし、それはフィールドのサイズやタイプなどの複数の要因に依存します。
Trendfischer

1
私はあなたの説明を本当に楽しんだ。ありがとうございました。
unpairestgood

43

まず、最初に2つを比較するには、クエリをサブクエリと区別して、次のことを行う必要があります。

  1. ジョインで記述された対応する同等のクエリが常にあるサブクエリのクラス
  2. 結合を使用して書き換えることができないサブクエリのクラス

最初のクラスのクエリの場合、優れたRDBMSは結合とサブクエリを同等と見なし、同じクエリプランを生成します。

最近ではmysqlでもそれを行っています。

それでも、そうでない場合もありますが、これは結合が常に勝つという意味ではありません。mysqlでサブクエリを使用するとパフォーマンスが向上する場合がありました。(たとえば、mysqlプランナーがコストを正しく見積もることができない場合、およびプランナーがjoin-variantとsubquery-variantを同じように認識しない場合、サブクエリは特定のパスを強制することにより、結合よりも優れたパフォーマンスを発揮します)。

結論としては、どちらの方がパフォーマンスが良いかを確認したい場合は、結合とサブクエリの両方のクエリをテストする必要があります。

2番目のクラスの場合、これらのクエリは結合を使用して書き換えることができないため、比較は意味がありません。これらの場合、サブクエリは必要なタスクを実行する自然な方法であり、それらを区別しないでください。


1
結合に変換できないサブクエリを使用して記述されたクエリの例を提供できますか(それを2番目のクラスと呼びます)。
Zahra、

24

引用された回答で強調されていないのは、特定の(使用)ケースから発生する可能性のある重複および問題のある結果の問題です。

(マルセロカントスはそれを言及していますが)

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(大学に関係なく)専攻に応募した学生の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、joinベースのステートメントでを使用しても、スコアの3つのオカレンスのうち1つが誤って保持されるため、問題が解消されないことも明らかです3.9。正しいケースは、クエリ基準に一致するスコアを持つ2人の生徒が実際にいる場合、スコアの2回の発生を考慮に入れることです。3.9

パフォーマンスの問題以外に、サブクエリが最も安全な方法である場合があります。


ここではサブクエリを使用できないと思います。これは論理的に使用できる場合ではありませんが、技術的な実装のために間違った答えを出します。これは、CSに属していない学生がスコアのINリストにある3.9をスコアできるため、サブクエリを使用できない場合です。CSのコンテキストは、サブクエリが実行されると失われますが、これは論理的に必要なことではありません。したがって、これはどちらも使用できる良い例ではありません。幸いにも別のデータセットに対して正しい結果が得られたとしても、サブクエリの使用はこのユースケースでは概念的/論理的に間違っています。
Saurabh Patil

22

SQL ServerのMSDNドキュメントによると

サブクエリを含む多くのTransact-SQLステートメントは、代わりに結合として作成できます。その他の質問は、サブクエリでのみ提起できます。Transact-SQLでは、通常、サブクエリを含むステートメントと、含まない意味的に同等のバージョンの間にパフォーマンスの違いはありません。ただし、存在を確認する必要がある場合には、結合を使用するとパフォーマンスが向上します。それ以外の場合は、重複を確実に排除するために、ネストされたクエリを外部クエリの結果ごとに処理する必要があります。そのような場合、結合アプローチはより良い結果をもたらします。

だからあなたが何かのようなものが必要な場合

select * from t1 where exists select * from t2 where t2.parent=t1.id

代わりに結合を使用してください。それ以外の場合、違いはありません。

私は言います:サブクエリ用の関数を作成することはcluttterの問題を排除し、サブクエリに追加のロジックを実装することを可能にします。したがって、可能な限りサブクエリの関数を作成することをお勧めします。

コードの乱雑さは大きな問題であり、業界は何十年もの間それを回避することに取り組んできました。


9
一部のRDBMS(Oracleなど)では、サブクエリを関数で置き換えることはパフォーマンス面で非常に悪い考えです。そのため、逆のことをお勧めします。可能な限り、関数ではなくサブクエリ/結合を使用してください。
フランクシュミット

3
@FrankSchmittは、参照を使用して引数をサポートしてください。
UğurGümüşhan

2
存在を確認しても、結合ではなくサブクエリを使用する必要がある場合もありますNOT EXISTS。a NOT EXISTSLEFT OUTER JOIN 、さまざまな理由でに勝っています:パフォーマンス、フェイルセーフ(nulable列の場合)、読みやすさ。sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter 2013年

16

古い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よりも、理解するのにはるかに長い時間がかかると考えられます。


1
最後の行であなたが指摘しようとしていることについてもっと知りたいのですが、「DISTINCT mos_content.catidは、DISTINCT mos_categories.idが理解するよりもはるかに時間がかかると思います。」。idには名前のみidを付け、次のような名前を付けないようにすべきcatidですか?私のdbアクセスを最適化しようとすると、あなたの学習が役立つ可能性があります。
bool.dev '21年

2
その場合にSQL INを使用することは悪い習慣であり、何も証明されません。
UğurGümüşhan

15

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秒


私は同意します。クエリを分割することも機能する場合があります。レコードが100万ある場合、結合は永久に使用されるため、結合を使用したくありません。むしろコード内で処理し、コード内でマップする方が優れています。
user1735921 2017年

1
結合が十分に速く機能していない場合は、インデックスが不足している可能性があります。クエリアナライザーは、実際のパフォーマンスを比較するのに非常に役立ちます。
digital.aaron、2017

私はAjay Gajeraに同意します。これは自分で見たことがあります。
user1735921

14
異なる結果を返す2つのクエリのパフォーマンスを比較することはどのように意味がありますか?
Paul Spiegel

はい、それらは、異なる複数の問合せが、同じ結果を返すです
ネオ王

12

サブクエリは通常、単一の行をアトミック値として返すために使用されますが、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などとは明らかに異なります。

速度が心配な場合は、データベースを確認して適切なクエリを作成し、パフォーマンスに大きな違いがあるかどうかを確認する必要があります。


11

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;

EXPLAINショー:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| 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は両方のクエリに対して同じ数の行を返し、これらのクエリはどちらも同等に高速に実行されます。


私も似たような状況ですが、あなたよりも多くの参加者がいるので、一度説明してみます
pahnin

OracleまたはPostgreSQLで私は試してみました:AND NOT EXISTS(SELECT 1 FROM list_tag WHERE list_id = l.list_id AND tag_id in(43、55、246403))
David Aldridge

11

サブクエリには、オンザフライで集計関数を計算する機能があります。たとえば、本の最低価格を見つけて、この価格で販売されているすべての本を取得します。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;

別のケース:GROUP BY異なるテーブルを持つ複数のs:stackoverflow.com/questions/11415284/…サブクエリは厳密により一般的なようです。MySQLのman:dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | も参照してください。dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html
Ciro Santilli冠状病毒审查六四事件法轮功

6
-1両方の例でサブクエリと結合を使用しているため、これは誤解を招く可能性があります。データベースがまったく同じことを行うので、最低の注文価格を決定するためにサブクエリを2番目のクエリに引き出しても効果はありません。さらに、サブクエリを使用して結合を書き直す必要はありません。どちらのクエリも結合を使用します。サブクエリが集約関数を許可するの正しいです、この例ではその事実を示していません。
David Harkness

私はデイビッドに同意します、そしてあなたは最低価格を得るためにgroup byを使うことができます。
user1735921

9
  • 原則として、ほとんどの場合、結合は高速です(99%)。
  • データテーブルの数が多いほど、サブクエリは遅くなります。
  • データテーブルの数が少ないほど、サブクエリ結合と同等の速度になります
  • サブクエリは、単純に理解しやすく、読みやすくなります。
  • ほとんどのウェブフレームワークとアプリフレームワーク、およびそれらの「ORM」と「アクティブレコード」は、サブクエリを使用してクエリを生成します。これは、サブクエリを使用すると、責任の分割やコードの保守などが容易になるためです。
  • 小規模なWebサイトまたはアプリではサブクエリは問題ありませんが、大規模なWebサイトおよびアプリでは、クエリがクエリで多くのサブクエリを使用する場合は特に、クエリを結合するために生成されたクエリを書き直す必要があります。

一部の人々は、「いくつかのRDBMSを書き換えることができると言う副問合せをする参加したり参加サブクエリ。それは1が速く、他のよりも思ったとき」が、この文は、単純な場合に、確実ではないとの複雑なクエリに適用されるサブクエリ、実際にA原因パフォーマンスの問題。


>しかし、このステートメントは単純なケースに適用されます。RDBMSによって「JOIN」に書き換えられる単純なケースか、サブクエリが適切であるような複雑なケースであることを理解しています。:-) ORMの良い点。これが最も大きな影響を与えると思います。
ピラット

4

違いは、2番目の結合テーブルにプライマリテーブルよりもかなり多くのデータがある場合にのみ見られます。以下のような経験をした...

10万エントリのusersテーブルとそのメンバーシップデータ(友情)が約30万エントリありました。これは、友人とそのデータを取得するための結合ステートメントでしたが、大幅に遅延しました。しかし、メンバーシップテーブルに少量のデータしかなかった場合、問題なく機能していました。サブクエリを使用するように変更したら、問題なく動作しました。

しかし、その間、結合クエリは、プライマリテーブルよりもエントリが少ない他のテーブルで機能しています。

だから私は結合とサブクエリステートメントがうまく機能していると思います、それはデータと状況に依存します。


3

最近では、多くのデータベースがサブクエリと結合を最適化できます。したがって、explainを使用してクエリを調べ、どちらが高速かを確認する必要があります。パフォーマンスに大きな違いがない場合は、サブクエリを使用することをお勧めします。サブクエリはシンプルで理解しやすいためです。


1

同じ問題について考えているだけですが、FROMの部分でサブクエリを使用しています。大きなテーブルから接続してクエリを実行する必要があります。「スレーブ」テーブルには2,800万件のレコードがありますが、結果は128しかないので、結果はビッグデータです。MAX()関数を使用しています。

最初はLEFT JOINを使用しています。それが正しい方法だと思うので、mysqlは最適化できるなどです。2回目は、テストのためだけに、JOINに対してサブ選択するように書き換えます。

LEFT JOINランタイム:1.12秒SUB-SELECTランタイム:0.06秒

ジョインよりもサブセレクトが18倍高速です!チョキト前売で。副選択はひどいように見えますが、結果...


-1

結合を使用してクエリを高速化する場合:

「内部結合/結合」の場合は、「ON」条件で使用する代わりにwhere条件を使用しないでください。例えば:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

「左/右結合」の場合、「オン」条件では使用しないでください。左/右結合を使用すると、1つのテーブルのすべての行が取得されるため、「オン」では使用しないでください。だから、「どこ」の条件を使用してみてください


これは、SQLサーバーとクエリの複雑さによって異なります。多くのSQL実装は、このような単純なクエリを最適化して、最高のパフォーマンスを実現します。おそらく、この動作が発生して答えが改善されるサーバー名とバージョンの例を提供しますか?
Trendfischer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.