テック技報最新号へ →
コード

Postgres / SQL: サブクエリの NOT IN は NULL が1つあると0件になる。NOT EXISTS を使う

x NOT IN (SELECT ...) は、サブクエリの結果に NULL が1行でも混ざると、どの行に対しても真にならず結果が0件になる。除外条件は NOT EXISTS で書く。

@amitsinha__07 · 投稿 2026-10-27
#postgres#sql#infra

なぜ起きるか

x NOT IN (1, NULL) は x <> 1 AND x <> NULL と同じ意味になる。x <> NULL は常に NULL(不明)なので、全体は「偽」か「NULL」にしかならず、WHERE を通らない。

sql
-- 「注文したことがないユーザー」を探すつもり
SELECT * FROM users
WHERE id NOT IN (SELECT user_id FROM orders);
-- orders.user_id に NULL が1行でもあると 0 件(エラーにもならない)

ゲスト注文などで user_id が NULL になる行が後から入ると、ある日突然クエリが何も返さなくなる。テストデータに NULL が無いと気づけない。

やり方

sql
SELECT * FROM users u
WHERE NOT EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);
  • NOT EXISTS は「一致する行があるか」だけを見るので、NULL の行は単に一致しないだけで結果を壊さない。
  • Postgres では NOT EXISTS は anti-join(Hash Anti Join など)として計画されることが多く、速度面でも有利なことが多い。NOT IN (サブクエリ) は NULL の扱いのせいで anti-join に変換できず、遅いプランになりやすい。
  • LEFT JOIN ... WHERE o.user_id IS NULL でも同じ結果になる。

注意点

  • 値のリストを直書きする NOT IN (1, 2, 3) は、リストに NULL を入れない限り問題ない。危ないのはサブクエリや、アプリから組み立てた配列に NULL が混ざる場合。
  • どうしても NOT IN を使うなら、サブクエリ側に WHERE user_id IS NOT NULL を付ける。
  • IN のほうは NULL が混ざっても一致する行は返るので、この問題は NOT IN 特有。

出典

X の元の投稿を開く