PostgreSQL で 関連テーブルに行が存在しない場合 に特定のデータを取得したいとき、NOT EXISTS は非常に有効です。
結合(LEFT JOIN + IS NULL)を使う方法もありますが、結合条件を間違えると 取りこぼし が起きやすいため、正確性を重視する場合は NOT EXISTS の方が安全です。
1. 基本の書き方
SELECT *
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
);
ポイント:
サブクエリが 1件でも返ると NOT EXISTS は false。
サブクエリが 0件なら true となり、外側の行が返る。
サブクエリの SELECT 1 の部分は何を返してもよい(値は使わない)。
2. なぜ結合では取りこぼす可能性があるのか
SELECT u.*
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.user_id IS NULL;
この方法でも「注文のないユーザー」を取得できます。
しかし 結合条件を複雑にすると、意図した結果が抜けることがある。
例えば複数テーブルを結合すると、条件によっては孤立行が NULL にならず除外される場合があります。
NOT EXISTS はサブクエリ単体で存在確認をするので、複雑な結合でも取りこぼしが起きません。
3. NULL が混ざると NOT IN は危険
SELECT *
FROM users
WHERE id NOT IN (SELECT user_id FROM orders);
orders.user_id に NULL があると、結果が 0件になる ことがあります。
安全性を考えると、NOT EXISTS が推奨です。
4. 実務での使いどころ
注文がないユーザーを抽出する
関連レコードが存在しないデータをチェックする
データ整合性確認の自動化
5. まとめ
NOT EXISTS は 「サブクエリが0件なら true」 の条件で行を抽出できる。
結合による LEFT JOIN + IS NULL は場合によって取りこぼしがある。
NOT IN は NULL に弱い。
よって 正確性を求める場合は NOT EXISTS がベスト。