🔖

【SQL】サブクエリの基本と IN / EXISTS の使い分け

に公開

はじめに

SQLで複雑な条件を指定したいときに便利なのが「サブクエリ」です。
特に IN (SELECT ...) のような形は、条件に合致するデータを他のテーブルから動的に絞り込みたい場面で活躍します。

ただし、サブクエリを使うときに気をつけたいのが NULLの扱いパフォーマンスの違いです。

本記事では、サブクエリの基本構文から注意点、INEXISTSの違いまでを整理して解説します。

テーブル例

以下の3テーブルを使って解説します。

users テーブル(ユーザー情報)

+----+----------+
| id | name     |
+----+----------+
| 1  | Alice    |
| 2  | Bob      |
| 3  | Carol    |
+----+----------+

orders テーブル(注文情報)

+----+---------+------------+
| id | user_id | product_id |
+----+---------+------------+
| 1  | 1       | 101        |
| 2  | 1       | 102        |
| 3  | 3       | 103        |
| 4  | NULL    | 104        |
+----+---------+------------+

products テーブル(商品情報)

+-----+----------+--------+
| id  | name     | stock  |
+-----+----------+--------+
| 101 | apple    | 10     |
| 102 | banana   | 0      |
| 103 | melon    | 3      |
| 104 | orange   | 0      |
+-----+----------+--------+

1. サブクエリとは?

サブクエリとは、クエリの中に埋め込まれた別のSELECT文のことです。

SELECT * FROM users
WHERE id IN (
  SELECT user_id FROM orders
);

このクエリでは、「orders テーブルに登場する user_id を持つ users」を取得しています。

2. よくある使い方

✅ 購入経験のあるユーザー一覧を取得

SELECT * FROM users
WHERE id IN (
  SELECT user_id FROM orders
);

結果:

+----+----------+
| id | name     |
+----+----------+
| 1  | Alice    |
| 3  | Carol    |
+----+----------+

✅ 在庫切れ商品を含む注文を取得

SELECT * FROM orders
WHERE product_id IN (
  SELECT id FROM products WHERE stock = 0
);

結果:

+----+----------+-------------+
| id | user_id  | product_id  |
+----+----------+-------------+
| 2  | 1        | 102         |
| 4  | NULL     | 104         |
+----+----------+-------------+

3. 注意点:NULL があると意図しない結果に

サブクエリの結果に NULL が含まれている場合、
比較結果が「不明(UNKNOWN)」と見なされ、条件式全体が成立しなくなることがあります。

❌ NOT IN + NULL で全行除外される

SELECT * FROM users
WHERE id NOT IN (
  SELECT user_id FROM orders
);

orders.user_idNULL があると、
上記のクエリは、すべての行が除外されてしまい、結果が0件になる可能性があります

🔍 詳しくは、SQLの NOT IN と NULL:データが取得できない原因とその回避法 をご覧ください。

4. INEXISTS の違い

INEXISTS、どちらを使えばいいのか?」という疑問はよくあります。
見た目は似ていても、仕組みや特性が異なります。

IN:リストに含まれているかどうかを見る

IN は、サブクエリの結果を「リスト」として扱い、その中に指定した値が含まれているかを調べます。

SELECT * FROM users
WHERE id IN (
  SELECT user_id FROM orders
);

このクエリは、「orders テーブルに user_id として登場しているユーザーだけを取得する」という意味になります。

注意点:
サブクエリに NULL が含まれていると、正しく比較できないことがあります。
例えば NOT IN の場合、1つでも NULL が混ざると 全体が無効になる可能性があります。

EXISTS:行が存在するかどうかを調べる

EXISTS は、サブクエリの結果が「1行でも存在するかどうか」だけをチェックします。
つまり、値そのものではなく「存在の有無」に着目しているのが特徴です。

SELECT * FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

このクエリは、orders テーブルに注文が1件でも存在するユーザーを取得します。

メリット:

  • NULL の影響を受けにくい
  • データ量が多い場合に効率的な実行計画を選びやすい(DBエンジンによる)

💡 同じ結果になる例

-- IN
SELECT * FROM users u
WHERE id IN (
  SELECT user_id FROM orders
);

-- EXISTS
SELECT * FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

いずれも以下の結果を返します。

+----+----------+
| id | name     |
+----+----------+
| 1  | Alice    |
| 3  | Carol    |
+----+----------+

5. INEXISTS の使い分け

ケース おすすめ
リストと一致する値をそのまま使いたい IN
サブクエリに NULL が含まれる可能性がある EXISTS
パフォーマンスを最適化したい(特に大量データ) EXISTS
確実に小さな固定リストで比較するだけ IN

※最適な選択は 使用するRDBMSやデータ量・インデックス状況にもよります。
どちらも試してみて、実行計画やパフォーマンスで判断するのが理想です。

おわりに

IN (SELECT ...) は、他のテーブルを条件に使える便利な構文ですが、

  • NULL による比較失敗
  • NOT IN による全件除外
  • パフォーマンス差

といった落とし穴に気づかないまま使うと、意図しない挙動につながります。

思ったようにデータが取れないときは、EXISTS への書き換えもぜひ検討してみてください。

本記事が参考になれば幸いです。

Discussion