SQLでサブクエリを使って除外条件を指定する際に利用される「NOT IN」と「NOT EXISTS」。両者の動作の違いやNULLの扱い、パフォーマンス差を実例付きで徹底解説します。
EXISTS は ANSI SQL(国際標準SQL)に含まれる構文 のため、
ほぼすべてのリレーショナルデータベースで利用できます。
古いバージョンの一部DBを除き、標準構文として移植性が非常に高いのが特徴です。
1. NOT IN と NOT EXISTS の基本構文
| 句 | 構文例 | 説明 | 
|---|---|---|
| NOT IN | SELECT * FROM A WHERE ID NOT IN (SELECT ID FROM B); | サブクエリの結果に含まれないIDを抽出 | 
| NOT EXISTS | SELECT * FROM A WHERE NOT EXISTS (SELECT 1 FROM B WHERE A.ID = B.ID); | Bに同じIDが存在しない場合のみAを取得 | 
ポイント:
- 
両者とも「除外」目的だが、評価タイミングとNULL処理が異なる。 
2. 動作の違い(NULLの扱いに注目)
| 条件 | NOT INの結果 | NOT EXISTSの結果 | 
|---|---|---|
| サブクエリにNULLが含まれる | すべての行が除外される | 正常に比較できる | 
| サブクエリが空(0件) | 全件取得される | 全件取得される | 
理由:NOT IN は内部的に「A.ID <> B.ID」を繰り返すような処理を行うため、NULLが含まれると比較結果がUNKNOWNとなり、全体が評価されなくなる。
一方、NOT EXISTS は行ごとに存在チェックを行うため、NULLの影響を受けない。
3. 実行結果の比較例
以下の例を見てみましょう。
テーブルA
| ID | NAME | 
|---|---|
| 1 | 田中 | 
| 2 | 鈴木 | 
| 3 | 佐藤 | 
テーブルB
| ID | 
|---|
| 1 | 
| NULL | 
4. パフォーマンスの違い
| 比較項目 | NOT IN | NOT EXISTS | 
|---|---|---|
| NULLの影響 | 受ける | 受けない | 
| 実行計画(最適化) | インデックス利用されにくい場合あり | 最適化されやすい | 
| 大量データ時の効率 | 遅くなるケースあり | より安定して高速 | 
| Oracleの最適化傾向 | 半結合(Anti-Join)に変換されることあり | 同様に最適化される | 
実測例(概略)
| 件数 | NOT IN所要時間 | NOT EXISTS所要時間 | 
|---|---|---|
| 1万件 | 0.25秒 | 0.20秒 | 
| 10万件 | 3.1秒 | 1.8秒 | 
※ 実測環境:Oracle 19c、インデックスあり、CPU 4コア相当
5. どちらを使うべきか
| 条件 | 推奨句 | 
|---|---|
| サブクエリにNULLが含まれる可能性あり | NOT EXISTS | 
| データが小規模でNULLなし | どちらでも可 | 
| 大規模データ・実行計画を重視 | NOT EXISTS(推奨) | 
| 可読性を優先 | NOT EXISTS のほうが誤動作が少ない | 
6. まとめ
| 観点 | 内容 | 
|---|---|
| ANSI SQL対応 | ○(どのDBでも使用可能) | 
| 実行パフォーマンス | DBごとに最適化される(MySQL 8以降で特に改善) | 
| 推奨度 | 高い(NOT INより安全で移植性が高い) | 
| 注意点 | MySQL 5.x 以前では最適化が弱いケースがある | 
✔ 結論:
除外条件を指定する場合は、基本的に「NOT EXISTS」を使う方が安全で高速です。
ただし、NULLが確実に存在しないことが保証される小規模データではNOT INも選択肢になります。
