順番に読み進めながら学べます

WHERE 詳細 ③ — IS NULL と IN で NULL・集合の絞り込み

WHERE 詳細の 3 本目は NULL と集合での絞り込みです。3 値論理、IS NULL / IS NOT NULL、IN / NOT IN、NOT IN と NULL の落とし穴まで、CSV の顧客データで学べます。

本記事で使うデータ — customer テーブル(NULL を含む)

WHERE 詳細の 3 本目は NULL集合での絞り込み を扱います。題材は CSV から自動ロードする customer テーブル(8 行)で、age / email / country の各列に NULL(値が不明) の行が混じっています。実務のテーブルでも「メールアドレスが未登録」「国が空」のように、必須項目以外は NULL が混じるのが普通です。

本記事の 5 演習では、まず NULL の正しい取り出し方(IS NULL / IS NOT NULL)を押さえ、続いて候補リストとの照合(IN / NOT IN)、最後に NULL を含むデータで NOT IN を使うときに 見落としがちな落とし穴 までを順に体験します。

演習に入る前に、customer テーブルの 列定義データのサンプル を確認しておきます。

PRAGMA table_info(customer); で列名・型・主キーを確認してください。

SELECT * FROM customer LIMIT 5; で先頭 5 行のデータをプレビューしてください。NULL になっているセルがあることを観察してください。

SQL エディタ

クエリを実行してください

NULL と 3 値論理 — 真・偽・不明

プログラミング言語では条件は TRUE(真)か FALSE(偽) の 2 択ですが、SQL ではもう 1 つ NULL(不明) という状態が加わります。これを 3 値論理 と呼びます。NULL は「値が空である」ではなく 「値が分からない」 という意味で、NULL = NULL ですら NULL(不明) になります(不明と不明を比較しても、それが等しいかは不明なため)。

WHERE 条件条件が TRUE になった行だけ を残します。NULL や FALSE は 両方とも除外 されます。このルールを覚えておくと、なぜ WHERE email = NULL が常に空っぽの結果を返すかが見えてきます。

SQL の 3 値論理
TRUE(真)FALSE(偽)NULL(不明)
条件式の評価結果はこの 3 種類のいずれか。WHERE は TRUE のときだけ行を残し、FALSE と NULL は同じ「除外」扱いになります。
3 値論理 — TRUE / FALSE / NULL の評価
比較式評価結果WHERE で残るかname = 'Alice'(name が Alice のとき)TRUE○ 残るname = 'Bob'(name が Alice のとき)FALSE× 除外email = NULL(email が NULL のとき)NULL× 除外(TRUE でない)email IS NULL(email が NULL のとき)TRUE○ 残る
WHERE は条件が TRUE の行だけを残します。NULL を含む比較は結果が NULL になり、NULL は TRUE ではないので除外されます。

なぜ `email = NULL` で取り出せないか

email = NULL という比較は、両辺のどちらかが NULL のとき 常に NULL(不明) を返します。'unknown' = NULLNULL = NULL も結果はすべて NULL です。WHERE は TRUE の行だけを残すので、NULL になった条件は 除外 され、結果は 0 行になります。

NULL を取り出すには、専用の 列 IS NULL / 列 IS NOT NULL を使う必要があります。これは「等価比較ではなく、NULL かどうかを直接確認する」専用の構文で、結果は必ず TRUE か FALSE のどちらかになります。

退会していない顧客が 0 人と表示された?

「退会していない顧客(deleted_at が NULL)の件数を出す」ときに、SELECT COUNT(*) FROM user WHERE deleted_at = NULL; と書いて結果が 0 で返ってきたことがあります。

実際には数千人の現役会員がいたのですが、deleted_at = NULL は常に NULL(不明)に評価されて WHERE で除外されるため、何人いても結果は 常に 0 人 でした。SQL を始めたばかりの頃よくあるミスなので、NULL 判定は 必ず `IS NULL` / `IS NOT NULL` を使うようにしましょう。

IS NULL と IS NOT NULL — 不明値を取り出す

NULL(値が不明)の行を取り出すには `列 IS NULL` を使います。逆に NULL 以外 の行を取り出すには 列 IS NOT NULL です。`= NULL` / `<> NULL` は使わない と覚えておくと、3 値論理の落とし穴を避けられます。

下のコードのとおり、書き方は = の代わりに IS を使うだけですが、結果はまったく違うものになります。= NULL の方は何も返らず、IS NULL の方は NULL の行を正しく取り出します。

`= NULL` と `IS NULL` の挙動の違い
WHERE email = NULLWHERE email IS NULLNULL = NULL→ NULLemail IS NULL→ TRUENULL は TRUE ではないので常に除外されるTRUE のとき残るのでNULL の行が取り出せる結果: 0 行結果: 4 行(Bob / Dave / Frank / Henry)
= NULL は常に NULL(不明)と評価されるため WHERE が 0 行になります。IS NULL は NULL であるかを直接判定し、TRUE / FALSE のどちらかを返します。
-- 1) NG: = NULL では取り出せない(常に NULL = 除外)
SELECT name, email FROM customer WHERE email = NULL;
-- 結果: 0 行

-- 2) OK: IS NULL で取り出す
SELECT name, email FROM customer WHERE email IS NULL;
-- 結果: 4 行(Bob / Dave / Frank / Henry)

-- 3) NULL 以外を取り出す
SELECT name, email FROM customer WHERE email IS NOT NULL;
-- 結果: 4 行(Alice / Carol / Eve / Grace)

顧客サポートで「メールアドレスが未登録の顧客リストを出して、登録依頼を送りたい」という要件を想定します。(正しく実行できれば解説が表示されます)

customer テーブルから nameemail の 2 列を取り出してください。

`email` 列が NULL の行に絞り込んでください。

③ 結果が 4 行(Bob / Dave / Frank / Henry、いずれも email が NULL)になることを確認してください。

SQL エディタ

クエリを実行してください

ターゲット広告の配信対象として「年齢とメールが両方登録されている顧客」を 年齢の高い順 に並べたい、という要件を想定します。

customer テーブルから nameageemail の 3 列を取り出してください。

`age` も `email` も NULL でない 行に絞り込んでください。

`age` の降順 で並べてください。

④ 結果が 3 行(Eve 42 / Grace 35 / Alice 30)になることを確認してください。

SQL エディタ

クエリを実行してください

IN と NOT IN — 候補リストとの照合

「country が JapanUSUK のいずれか」のように、列の値が候補リストのどれかに一致するか を判定したいときに使うのが IN です。列 IN (値1, 値2, ...) と書くと、候補のいずれかに一致した行を取り出せます。列 = 値1 OR 列 = 値2 OR 列 = 値3 と書くのと同じ意味で、可読性のために IN を選ぶ場面が多いです。

NOT IN を付ければ、候補のどれにも一致しない 行を取り出せます。

`IN` の動き — 候補リストとの照合
country の値IN ('Japan', 'US')WHERE で残るかJapanTRUE○ 残るUSTRUE○ 残るUKFALSE× 除外ItalyFALSE× 除外NULLNULL(不明)× 除外(NULL は TRUE でない)
IN は値が候補リストのいずれかと一致するかを判定します。列の値が NULL の行は IN / NOT IN ともに NULL として評価されて除外されるので、NULL が混じるデータでは別途 IS NULL / IS NOT NULL と組み合わせます。
-- 1) IN: 候補リストのいずれかに一致
SELECT name, country FROM customer
WHERE country IN ('Japan', 'US');

-- 上の式と同じ意味(OR での書き換え)
SELECT name, country FROM customer
WHERE country = 'Japan' OR country = 'US';

-- 2) NOT IN: 候補のどれにも一致しない
SELECT name, country FROM customer
WHERE country NOT IN ('Japan');

「日本またはアメリカの顧客にお知らせメールを送りたい」という要件を想定します。

customer テーブルから namecountry の 2 列を取り出してください。

`country` が `'Japan'` または `'US'` の行に絞り込んでください(IN を使ってください)。

③ 結果が 5 行(Alice Japan / Bob US / Carol Japan / Frank Japan / Grace US)になることを確認してください。

SQL エディタ

クエリを実行してください

`NOT IN` と NULL の落とし穴

NOT IN ('Japan') は「country が Japan でない人」を取り出すように見えますが、country が NULL の行も除外 されます。これは country NOT IN ('Japan') の評価が、内部的には country <> 'Japan'(= 等しくない)になり、NULL <> 'Japan'NULL(不明) を返すためです。NULL は TRUE ではないので WHERE が除外します。

さらに踏み込んで、候補リスト自体に NULL が混じった NOT IN ('Japan', NULL) のような書き方をすると、全行が NULL 評価で消える という事故も起きます。NULL が含まれる可能性のある列に NOT IN を使うときは、必ず column NOT IN (...) AND column IS NOT NULL のように NULL 除外を明示 するか、IN 側で書き換える設計を選びます。

「日本以外の在住顧客のうち、年齢の高い順に 2 名にプレミアム特典を案内したい」という要件を想定します。

customer テーブルから nameagecountry の 3 列を取り出してください。

`country` が `'Japan'` でないNOT IN)行に絞り込んでください。

`age` の降順 で並べ、先頭 2 行 に絞ってください。

④ 結果が 2 行(Grace 35 US / Henry 29 Italy)になることを確認してください。Eve(country が NULL)が除外されている理由は resultExplanation でも確認します。

SQL エディタ

クエリを実行してください

実践 4 の落とし穴を踏まえ、「日本在住でないことが確定している顧客」と「国そのものが未登録の顧客」の 両方 をまとめて出したい、という要件を想定します。

customer テーブルから nameagecountry の 3 列を取り出してください。

`country` が `'Japan'` 以外 の行に加えて、`country` が NULL の行も含めてください(OR でつなぎます)。

`age` の降順 で並べてください。

④ 結果が 5 行(Eve 42 NULL / Grace 35 US / Henry 29 Italy / Dave 28 UK / Bob 25 US)になることを確認してください。

SQL エディタ

クエリを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1次のうち、SELECT * FROM customer WHERE email = NULL; の結果として正しいものはどれですか。

Q2次のうち、IN を使った絞り込みの説明として正しいものはどれですか。

Q3country 列に NULL が混じった customer テーブルに対して WHERE country NOT IN ('Japan') を実行した時、country が NULL の行はどう扱われますか。