「テーブルAにはあるけどテーブルBにはないレコード」を取得したい、という場面はよくあります。たとえば未購入の顧客リストや未登録の商品など、差分を抽出するケースです。
この記事で学べること:片方のテーブルにしか存在しないデータを取得する3つの方法・パフォーマンスの違い・使い分けのポイント
サンプルテーブル
以下のテーブルを使って解説します。users(全ユーザー)とorders(購入済みユーザー)の差分から未購入ユーザーを取得する例です。
-- users(全ユーザー) user_id | name --------+------ 1 | 田中 2 | 佐藤 3 | 鈴木 4 | 高橋 -- orders(購入済みユーザーのID) user_id | product --------+-------- 1 | 商品A 3 | 商品B
方法1:LEFT JOIN + IS NULL
最も一般的な方法。LEFT JOINで全件結合し、右テーブルにない行(NULLになる行)を抽出します。
SELECT u.user_id, u.name FROM users u LEFT JOIN orders o ON u.user_id = o.user_id WHERE o.user_id IS NULL; -- 結果 -- user_id | name -- --------+------ -- 2 | 佐藤 -- 4 | 高橋
LEFT JOINのメリット:直感的でインデックスが効きやすく、大量データでも高速です。最もよく使われる方法です。
方法2:NOT IN
サブクエリの結果に含まれないレコードを取得します。SQLがシンプルで読みやすいです。
SELECT user_id, name
FROM users
WHERE user_id NOT IN (
SELECT user_id FROM orders
);
-- 結果: user_id=2(佐藤), user_id=4(高橋)
NOT INの注意点:サブクエリにNULLが含まれると結果が0件になります。
orders.user_idがNULLになり得る場合はWHERE user_id IS NOT NULLを追加してください。方法3:NOT EXISTS
相関サブクエリで存在しないレコードを絞り込みます。NULLに強く、大規模テーブルに向いています。
SELECT u.user_id, u.name
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.user_id
);
-- 結果: user_id=2(佐藤), user_id=4(高橋)
NOT EXISTSのメリット:サブクエリにNULLが含まれても正しく動作します。また、存在確認で止まるため大量データでも効率的です。
3つの方法の使い分けまとめ
| 方法 | 可読性 | NULL安全 | パフォーマンス | 推奨場面 |
|---|---|---|---|---|
| LEFT JOIN + IS NULL | ○ | ○ | ◎ | 大量データ・インデックスあり |
| NOT IN | ◎ | △(NULL注意) | ○ | 小〜中規模・サブクエリ件数少 |
| NOT EXISTS | ○ | ◎ | ○ | NULL対策必要・大規模テーブル |
よくある質問(FAQ)
Q. NOT INでNULLが含まれると結果が0件になるのはなぜですか?
A. SQLでは
NULL = NULLはTRUE(真)ではなくUNKNOWNになります。NOT INはリスト内の全要素との比較がFALSEの場合に選択されますが、NULLとの比較はUNKNOWNになるため、条件全体がFALSEにならず結果が0件になります。Q. 3つのテーブルで「Aにあり、BにもCにもない」レコードを取得するには?
A.
LEFT JOINを2回使います:FROM a LEFT JOIN b ON ... LEFT JOIN c ON ... WHERE b.id IS NULL AND c.id IS NULL。またはNOT EXISTSをAND NOT EXISTS(...)で組み合わせます。Q. MySQLとPostgreSQLで動作の違いはありますか?
A. どの方法も主要RDBMSで動作します。ただしNOT INのNULL問題はDBMS共通です。Oracleでは
MINUS演算子(PostgreSQLではEXCEPT)で差分を取る方法もあります。まとめ
- LEFT JOIN + IS NULL:最も汎用的・高速・インデックスが効く
- NOT IN:シンプルだが右テーブルにNULLがある場合は使用注意
- NOT EXISTS:NULL安全で大規模テーブルにも対応
迷った場合はLEFT JOIN + IS NULLを使うのが最も安全で高速です。NOT INはサブクエリ結果が少なく、NULLが含まれないと保証されている場合のみ使いましょう。