【SQL】サブクエリの基本と IN / EXISTS の使い分け
はじめに
SQLで複雑な条件を指定したいときに便利なのが「サブクエリ」です。
特に IN (SELECT ...)
のような形は、条件に合致するデータを他のテーブルから動的に絞り込みたい場面で活躍します。
ただし、サブクエリを使うときに気をつけたいのが NULLの扱いやパフォーマンスの違いです。
本記事では、サブクエリの基本構文から注意点、IN
とEXISTS
の違いまでを整理して解説します。
テーブル例
以下の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 |
+----+----------+-------------+
NULL
があると意図しない結果に
3. 注意点:サブクエリの結果に NULL
が含まれている場合、
比較結果が「不明(UNKNOWN)」と見なされ、条件式全体が成立しなくなることがあります。
❌ NOT IN + NULL で全行除外される
SELECT * FROM users
WHERE id NOT IN (
SELECT user_id FROM orders
);
orders.user_id
に NULL
があると、
上記のクエリは、すべての行が除外されてしまい、結果が0件になる可能性があります。
🔍 詳しくは、SQLの NOT IN と NULL:データが取得できない原因とその回避法 をご覧ください。
IN
と EXISTS
の違い
4. 「IN
と EXISTS
、どちらを使えばいいのか?」という疑問はよくあります。
見た目は似ていても、仕組みや特性が異なります。
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 |
+----+----------+
IN
と EXISTS
の使い分け
5. ケース | おすすめ |
---|---|
リストと一致する値をそのまま使いたい | IN |
サブクエリに NULL が含まれる可能性がある |
EXISTS |
パフォーマンスを最適化したい(特に大量データ) | EXISTS |
確実に小さな固定リストで比較するだけ | IN |
※最適な選択は 使用するRDBMSやデータ量・インデックス状況にもよります。
どちらも試してみて、実行計画やパフォーマンスで判断するのが理想です。
おわりに
IN (SELECT ...)
は、他のテーブルを条件に使える便利な構文ですが、
-
NULL
による比較失敗 -
NOT IN
による全件除外 - パフォーマンス差
といった落とし穴に気づかないまま使うと、意図しない挙動につながります。
思ったようにデータが取れないときは、EXISTS
への書き換えもぜひ検討してみてください。
本記事が参考になれば幸いです。
Discussion