0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SQL】NULLがある時のNOT IN・NOT EXISTSの違い

Posted at

はじめに

現在「達人に学ぶSQL徹底指南書」を読みながらSQLを学習中のTairaです

SQLの NOT INNOT 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 INNULL が絶対に入らない保証がある場合のみ
  • それ以外は NOT EXISTS を使う
  • 「〜していない」「〜を持っていない」は NOT EXISTS が自然

👉 迷ったら NOT EXISTS


補足:NOT IN を安全に使うなら

WHERE id NOT IN (
  SELECT id
  FROM RetiredEmployees
  WHERE id IS NOT NULL
);

ただし、書き忘れや将来のデータ変更で壊れやすいため、設計としては推奨されません。


まとめ

  • NOT INNOT (A OR B)A AND B という論理変換により NULL に弱い
  • NOT EXISTS は相関サブクエリによる行単位評価のため NULL がロジックを壊さない
  • 安全・意図が明確なのは NOT EXISTS
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?