メインコンテンツへスキップ

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
サブクエリと JOIN は、多くの場合同じ結果を生成します。どちらを選ぶかは、読みやすさ、NULL に対する正しさ、そして場合によってはパフォーマンスに影響します。

サブクエリの種類

非相関サブクエリは一度だけ実行され、その結果が外側のクエリで使われます。
相関サブクエリは外側のクエリの列を参照するため、行ごとに一度実行されます。
相関サブクエリは大きなテーブルでは遅くなることがあります。外側のクエリのすべての行に対してサブクエリが実行されるためです。MySQL がどう実行しているかは EXPLAIN で確認してください。

IN と EXISTS と JOIN の比較

この 3 つのパターンは、多くの場合同じ問題を解決します。
MySQL のオプティマイザは IN (subquery) を内部的にセミジョインに書き換えることが多いため、パフォーマンスの差は無視できることがよくあります。サブクエリの結果セットが大きい場合は、EXISTS または JOIN のほうが信頼できます。

NOT IN の NULL 問題

NOT IN は、サブクエリが 1 つでも NULL を返すと予想外の動作をします。
orders.customer_id に NULL の行が 1 つでもあると、NOT IN 全体が空を返します。これは正しい SQL の動作です。NULL は不明なので、NOT IN (NULL, 1, 2) はすべての比較に対して UNKNOWN になります。代わりに NOT EXISTS を使ってください。
MySQL の NULL の動作の詳細は、MySQL の NULLを参照してください。

FROM 内のサブクエリ(派生テーブル)

FROM 句のサブクエリは派生テーブルを作ります。
このパターンでは、通常 CTE のほうが分かりやすくなります。ロジックがインラインで入れ子になるのではなく、先頭で名前を付けて定義されるためです。MySQL の CTE が派生テーブルより優れる場合を参照してください。

パフォーマンスに関する考慮事項

MySQL のオプティマイザは、多くのサブクエリのパターンを書き換えます。手動で最適化する前に、次のことを行ってください。
  1. サブクエリ版と JOIN 版の両方に EXPLAIN を実行し、MySQL が実際に何を実行しているかを確認する
  2. 結合列や相関列にインデックスがあることを確認する。通常はここがボトルネックになります
  3. EXPLAIN が実行計画に大きな差を示す場合にのみ書き換える

よくある質問

JOIN は常にサブクエリより速いですか?

必ずしもそうではありません。MySQL 8.0 のオプティマイザは、多くの場合 IN (subquery) をセミジョインに書き換え、明示的な JOIN と同じ実行計画を生成します。一方が常に速いと決めつけず、EXPLAIN で測定してください。

IN の代わりに EXISTS を使うべきなのはいつですか?

一致が存在するかどうかだけが重要な場合(一致したデータの内容は不要)、サブクエリが NULL を返す可能性がある場合、またはサブクエリが大きな結果セットを返す場合に EXISTS を使います。EXISTS は最初の一致で短絡しますが、IN は完全なリストを構築する必要があります。

セミジョインとは何ですか?

セミジョインは、右側に少なくとも 1 つの一致する行がある左側のテーブルの行を返します。複数の一致があっても、左側のテーブルの行を重複して生成しません。MySQL のオプティマイザは、IN (subquery) と EXISTS を効率的に実行するために、内部的にセミジョインを使います。SQL でセミジョインを直接書くことはできませんが、EXISTS と IN (subquery) はどちらも、オプティマイザにセミジョインの検討を促します。

トラブルシューティング

関連項目