Q1次のうち、SELECT * FROM customer WHERE email = NULL; の結果として正しいものはどれですか。
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 を使うときに 見落としがちな落とし穴 までを順に体験します。
NULL と 3 値論理 — 真・偽・不明
プログラミング言語では条件は TRUE(真)か FALSE(偽) の 2 択ですが、SQL ではもう 1 つ NULL(不明) という状態が加わります。これを 3 値論理 と呼びます。NULL は「値が空である」ではなく 「値が分からない」 という意味で、NULL = NULL ですら NULL(不明) になります(不明と不明を比較しても、それが等しいかは不明なため)。
WHERE 条件 は 条件が TRUE になった行だけ を残します。NULL や FALSE は 両方とも除外 されます。このルールを覚えておくと、なぜ WHERE email = NULL が常に空っぽの結果を返すかが見えてきます。
なぜ `email = NULL` で取り出せないか
email = NULL という比較は、両辺のどちらかが NULL のとき 常に NULL(不明) を返します。'unknown' = NULL も NULL = 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 の行を正しく取り出します。
-- 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)
IN と NOT IN — 候補リストとの照合
「country が Japan か US か UK のいずれか」のように、列の値が候補リストのどれかに一致するか を判定したいときに使うのが IN です。列 IN (値1, 値2, ...) と書くと、候補のいずれかに一致した行を取り出せます。列 = 値1 OR 列 = 値2 OR 列 = 値3 と書くのと同じ意味で、可読性のために IN を選ぶ場面が多いです。
NOT IN を付ければ、候補のどれにも一致しない 行を取り出せます。
-- 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');
`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 側で書き換える設計を選びます。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2次のうち、IN を使った絞り込みの説明として正しいものはどれですか。
Q3country 列に NULL が混じった customer テーブルに対して WHERE country NOT IN ('Japan') を実行した時、country が NULL の行はどう扱われますか。