【SQL】片方のテーブルにしか存在しないデータを取得する方法

「テーブル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 EXISTSAND 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が含まれないと保証されている場合のみ使いましょう。