SQLの NOT IN と NULL:データが取得できない原因とその回避法
はじめに
SQLで NOT IN を使って「特定の条件に当てはまらないデータを抽出しよう」としたのに、
なぜかデータが返ってこない…そんな経験はありませんか?
この原因の多くは、NULL の存在によるものです。
NOT IN は便利ですが、NULL が含まれると意図しない結果になる可能性があります。
本記事では、具体的なテーブルを例にとりながら、
NOT IN と NULL の関係、そして安全に使うための方法を紹介します。
1. NOT IN の基本:例で理解する
例えば、users テーブルがあるとします。
-- users テーブル
+----+----------+
| id | name |
+----+----------+
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| 4 | David |
+----+----------+
ここで、「id が 2, 3 ではないユーザーを取得したい」ときは以下のように書きます。
SELECT * FROM users
WHERE id NOT IN (2, 3);
結果:
+----+--------+
| id | name |
+----+--------+
| 1 | Alice |
| 4 | David |
+----+--------+
このように、NOT IN を使うと指定した値以外の行を簡単に取得できます。
2. 注意点:NULL が含まれると全て除外される
次に orders テーブルがあるとします。
-- orders テーブル
+----+---------+
| id | user_id |
+----+---------+
| 1 | 2 |
| 2 | NULL |
| 3 | 3 |
+----+---------+
ここで「orders テーブルに登場していない users を取得したい」と考え、
次のように書いたとします。
SELECT * FROM users
WHERE id NOT IN (
SELECT user_id FROM orders
);
しかし、結果は0件になります。
🔍 結果が0件になる理由
orders.user_id に NULL が含まれているため、比較できなくなってしまうのが原因です。
SQLでは以下のようなルールがあります。
-
NULLは「値が不明」なので、比較ができません。- たとえば
id = NULLやid != NULLのような比較は、どちらも「不明(UNKNOWN)」 という扱いになります。
- たとえば
-
NOT INは、中にNULLが1つでもあると、すべての比較が「不明」扱いになります。- つまり
id NOT IN (2, NULL)のような状態になると、どのidに対しても「比較できない」と判断されてしまいます。
- つまり
-
WHERE句では「不明」はfalseと同じ扱いになります。- そのため、全行が除外されてしまうのです。
💡「1件でも
NULLが混じっていたら全部が不明扱いになる」──これがNOT INの落とし穴です。
3. 回避策:安全に使う方法
✅ 方法1:サブクエリで NULL を除外する
💡 補足: サブクエリとは、
SELECT文の中にさらに別のSELECTを書く「入れ子のクエリ」のことです。
以下の例では、(SELECT user_id FROM orders ...)の部分がサブクエリです。
NULL を取り除いた上で NOT IN を使えば、期待通り動作します。
SELECT * FROM users
WHERE id NOT IN (
SELECT user_id FROM orders
WHERE user_id IS NOT NULL
);
結果:
+----+--------+
| id | name |
+----+--------+
| 1 | Alice |
| 4 | David |
+----+--------+
NULL を取り除いたことで、id = 2, 3 だけが除外され、正しく動作します。
✅ 方法2:NOT EXISTS を使う
より安全なのは NOT EXISTS を使う方法です。NULL の影響を受けず、確実に除外できます。
以下のクエリでは、orders テーブルに「同じ user_id を持つ行が存在しない」ユーザーのみを取得しています。
SELECT * FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);
💡 補足:
users uはusers AS uの省略形です。uは テーブルに付けた短縮名(エイリアス) で、後のクエリ内でusersをuと書けるようになります。SELECT 1は「特定の値(ここでは1)を返すだけ」の書き方で、実際に取得する値は重要ではないという意味です。NOT EXISTSは「データが存在するかどうか」を見ているので、SELECT 1でもSELECT *でも動作には影響ありません。
結果:
+----+--------+
| id | name |
+----+--------+
| 1 | Alice |
| 4 | David |
+----+--------+
4. まとめ
| パターン |
NULL の影響 |
推奨度 |
|---|---|---|
NOT IN |
ある(注意) | △ 注意が必要 |
NOT IN(NULL除外) |
回避可能 | ◯ 問題なし |
NOT EXISTS |
ない(安全) | ◎ 安心して使える |
おわりに
NOT IN を使うときに NULL が紛れ込んでいると、思わぬ全件除外が起こることがあります。
特に実務では、サブクエリ側の値に NULL が混じることは珍しくありません。
意図しない結果を防ぐためにも
-
NULLを除外する -
NOT EXISTSを使う
といった方法を使って、安全にクエリを書いていきましょう。
本記事が参考になれば幸いです。
🔗 関連リンク
Discussion