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で「存在しないデータ」を探すには?Anti JoinとNOT EXISTSの使い方

0
Last updated at Posted at 2026-09-08

はじめに

こんにちは、@snowqueen_tgです。

SQLでは、JOINを使って複数のテーブルから関連するデータを取得できます。
では、次のような「関連するデータが存在しない行」を探したい場合は、どのように書けばよいのでしょうか。

  • 一度も注文していない顧客
  • 部署に所属していない従業員
  • 商品が登録されていないカテゴリー

このような検索に使われる考え方が「Anti Join(アンチジョイン)」です。

今回は「注文履歴のない顧客」を例に、Anti Joinを実現する3つの書き方と注意点を整理してみました!

:thinking: Anti Joinとは?

Anti Joinは、一方のテーブルに存在し、もう一方のテーブルには対応するデータが存在しない行を取得する処理です。

例えば、次の2つのテーブルがあるとします。

customers

customer_id customer_name
1 Tanaka
2 Sato
3 Suzuki

orders

order_id customer_id
101 1
102 3

この場合、注文履歴がないのはcustomer_id = 2のSatoです。
ただし、一般的なSQLにはANTI JOINという構文が用意されているわけではありません。
主に次の方法で同じ処理を実現します。

  • NOT EXISTS
  • NOT IN
  • LEFT JOIN ... IS NULL

:one: NOT EXISTSを使う

最も意図が伝わりやすいのは、1NOT EXISTS1を使う方法です。

SELECT
    c.customer_id,
    c.customer_name
FROM customers AS c
WHERE NOT EXISTS (
    SELECT 1
    FROM orders AS o
    WHERE o.customer_id = c.customer_id
);

EXISTSは、サブクエリの結果が1行でも存在すればTRUEになります。

反対にNOT EXISTSは、条件に一致する行が存在しない場合にTRUEとなります。

この例では、各顧客について同じcustomer_idを持つ注文が存在するか確認し、存在しない顧客だけを取得しています。

結果は次のとおりです。

customer_id customer_name
2 Sato

「注文が存在しない顧客を探す」という目的をSQLから読み取りやすく、関連する列にNULLが含まれていても影響を受けにくい書き方です。

:two: NOT INを使う

同じ処理は、NOT INでも記述できます。

SELECT
    customer_id,
    customer_name
FROM customers
WHERE customer_id NOT IN (
    SELECT customer_id
    FROM orders
);

サブクエリが1と3を返す場合、それらに含まれない2が取得されます。
ただし、NOT INを使う場合はNULLに注意が必要です。

NULLが含まれている場合

例えば、サブクエリの結果にNULLが含まれているとします。

SELECT
    customer_id,
    customer_name
FROM customers
WHERE customer_id NOT IN (1, 3, NULL);

SQLにおけるNULLは、「値が存在しない」ではなく「不明」を表します。

そのため、customer_idがNULLと異なるかどうかを判定できず、条件全体がTRUEになりません。結果として、想定していた行が取得できない場合があります。

NOT INを使用するなら、対象列にNOT NULL制約が設定されているか確認するか、サブクエリ側でNULLを除外します。

SELECT
    customer_id,
    customer_name
FROM customers
WHERE customer_id NOT IN (
    SELECT customer_id
    FROM orders
    WHERE customer_id IS NOT NULL
);

ただし、NULLが含まれる可能性がある場合は、最初からNOT EXISTSを使ったほうが意図を明確に表現できます。

:three: LEFT JOINとIS NULLを使う

LEFT JOINとIS NULLを組み合わせる方法もあります。

SELECT
    c.customer_id,
    c.customer_name
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
WHERE o.customer_id IS NULL;

LEFT JOINでは、左側のcustomersにある行をすべて残します。

対応する注文がない場合、orders側の列にはNULLが入ります。そのため、WHERE o.customer_id IS NULLを指定すると、注文のない顧客だけを取得できます。

この方法はJOINに慣れている人には理解しやすい一方、IS NULLで確認する列の選び方には注意が必要です。
対応の有無を正しく判定できる、orders側のNOT NULL列を使用しましょう。

:pen_ballpoint: どの書き方を選ぶ?

3つの方法をまとめると

書き方 特徴 注意点
NOT EXISTS 処理の意図が分かりやすい 相関条件の書き忘れに注意
NOT IN 短く書きやすい サブクエリにNULLがあると結果が変わる
LEFT JOIN ... IS NULL JOINとして理解しやすい 判定に使う列と実行計画を確認する

基本的には、「対応するデータが存在しない」という処理の意図を明確に表現でき、NULLの影響も受けにくいNOT EXISTSが使いやすいでしょう。

ただし、NOT EXISTS、NOT IN、LEFT JOIN ... IS NULLのどれが最も速いかは、データベースの種類やバージョン、データ量、インデックス、統計情報によって異なります。

オプティマイザによって同様のAnti Joinとして処理される場合もあるため、大量のデータを扱う際は、EXPLAINなどで実行計画を確認することが大切です。

:pencil: まとめ

Anti Joinは、一方のテーブルに存在し、もう一方には対応するデータが存在しない行を探すための考え方です。

特に注意したいのが、NOT INとNULLの組み合わせです。対象列にNULLが含まれる可能性がある場合、想定した結果を取得できないことがあります。
迷った場合は、処理の意図が分かりやすく、NULLの影響も受けにくいNOT EXISTSから検討するとよいでしょう。

また、大量のデータを扱う場合は、SQLの書き方だけで判断せず、実行計画もあわせて確認することが重要です。


✨ちなみに、私が携わっているデータベース管理ツール「Navicat」では、SQLエディタを使ってクエリの作成・実行や結果の確認ができます。

製品のご購入をご希望の法人のお客様は、こちらをご利用ください。
TenGenesis株式会社日本総代理店:
https://japan-navicat.com/?utm_source=qiita&utm_medium=article


Navicat公式サイト:https://jp.navicat.com/
Linkedin:https://www.linkedin.com/company/navicat-japan/
X:https://x.com/navicat_jp

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?