はじめに
現在「達人に学ぶSQL徹底指南書」を読みながらSQLを学習中のTairaです
SQLの NOT INと NOT EXISTSについての違いを忘れる前にまとめておこうと思い記事にしました。
本記事では、
-
NOT INが NULL に弱い理由(論理変換を省略せずに) -
NOT EXISTSが NULL でも壊れない理由(評価の具体例で) - 両者の本質的な違い
を整理します。
前提となるテーブル
Employees
+----+----------+
| id | name |
+----+----------+
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
+----+----------+
RetiredEmployees
+----+
| id |
+----+
| 2 |
| NULL |
+----+
「退職していない社員を取得したい」ケースを考えます。
NOT IN を使った場合
SELECT *
FROM Employees
WHERE id NOT IN (
SELECT id FROM RetiredEmployees
);
結果
0 rows
全件が除外されます。
NOT EXISTS を使った場合
SELECT *
FROM Employees e
WHERE NOT EXISTS (
SELECT 1
FROM RetiredEmployees r
WHERE r.id = e.id
);
結果
id | name
-----------
1 | Alice
3 | Charlie
こちらは 期待どおりの結果 になります。
なぜ NOT IN は NULL があると壊れるのか
ここで重要なのは、いきなり id <> 2 AND id <> NULL と書かないことです。論理変換を段階的に追います。
① NOT IN の論理的意味
id NOT IN (2, NULL)
まず NOT を外に出します。
NOT (id IN (2, NULL))
② IN は OR の集合
id IN (2, NULL)
は論理的に次と等価です。
(id = 2 OR id = NULL)
③ NOT をかける(ド・モルガンの法則)
NOT (id = 2 OR id = NULL)
⬇︎
id <> 2 AND id <> NULL
④ 問題は id <> NULL
SQL では
id <> NULL
の評価結果は TRUE / FALSE ではなく UNKNOWN になります(SQL は三値論理を採用しているため)。
⑤ UNKNOWN は WHERE 句で除外される
TRUE AND UNKNOWN → UNKNOWN
FALSE AND UNKNOWN → FALSE
WHERE 句では
- TRUE → 採用
- FALSE / UNKNOWN → 除外
結果として、
サブクエリに NULL が 1 行でも含まれると、条件全体が UNKNOWN になり全件落ちる
これが NOT IN が NULL に弱い理由です。
NOT EXISTS が NULL でも壊れない理由(評価の具体例)
NOT EXISTS は 集合比較だから安全なのではありません。本質は 相関サブクエリによる行単位評価 にあります。
実際にどう評価されているかを、1 行ずつ見てみます。
クエリ(再掲)
SELECT *
FROM Employees e
WHERE NOT EXISTS (
SELECT 1
FROM RetiredEmployees r
WHERE r.id = e.id
);
外側の行ごとの評価
ケース① e.id = 1
SELECT 1
FROM RetiredEmployees r
WHERE r.id = 1;
| r.id | r.id = 1 |
|---|---|
| 2 | FALSE |
| NULL | UNKNOWN |
- WHERE 句では FALSE / UNKNOWN は除外
- → 行が 1 件も残らない
👉 EXISTS = FALSE
👉 NOT EXISTS = TRUE
👉 採用
ケース② e.id = 2
SELECT 1
FROM RetiredEmployees r
WHERE r.id = 2;
| r.id | r.id = 2 |
|---|---|
| 2 | TRUE |
| NULL | UNKNOWN |
- TRUE の行が 1 行でも存在
👉 EXISTS = TRUE
👉 NOT EXISTS = FALSE
👉 除外
ケース③ e.id = 3
SELECT 1
FROM RetiredEmployees r
WHERE r.id = 3;
| r.id | r.id = 3 |
|---|---|
| 2 | FALSE |
| NULL | UNKNOWN |
- TRUE になる行は存在しない
- UNKNOWN は WHERE 句で自然に落ちる
👉 EXISTS = FALSE
👉 NOT EXISTS = TRUE
👉 採用
ここが決定的な違い
-
r.id = e.idが UNKNOWN になる行(NULL)は 比較に失敗してその行だけが除外される - EXISTS は TRUE になる行が 1 行でもあるか しか見ていない
- UNKNOWN が条件全体に伝播しない
つまり NOT EXISTS は、
NULL を特別扱いしているのではなく、行単位評価の結果として自然に排除している
だけです。
NOT IN と NOT EXISTS の本質的な違い
| 観点 | NOT IN | NOT EXISTS |
|---|---|---|
| 評価単位 | 値の集合 | 行 |
| NULL の影響 | 論理式全体に伝播 | 行単位で消える |
| 安全性 | 低い | 高い |
| 実務向き | 条件付き | ◎ |
実務での結論
-
NOT INは NULL が絶対に入らない保証がある場合のみ - それ以外は NOT EXISTS を使う
- 「〜していない」「〜を持っていない」は NOT EXISTS が自然
👉 迷ったら NOT EXISTS
補足:NOT IN を安全に使うなら
WHERE id NOT IN (
SELECT id
FROM RetiredEmployees
WHERE id IS NOT NULL
);
ただし、書き忘れや将来のデータ変更で壊れやすいため、設計としては推奨されません。
まとめ
-
NOT INはNOT (A OR B)→A AND Bという論理変換により NULL に弱い -
NOT EXISTSは相関サブクエリによる行単位評価のため NULL がロジックを壊さない - 安全・意図が明確なのは NOT EXISTS