Q1次のうち、SELECT * FROM customer WHERE email = NULL;の結果として正しいものはどれですか。
WHERE 詳細 ③ — IS NULL と IN で NULL・集合の絞り込み
この記事は、基礎から複雑なSQL,SQLチューニングまでSQLの実践的なスキルを1からマスターする「SQL入門講座の一部」です。
3値論理(真・偽・不明)、IS NULL/IS NOT NULL、IN/NOT IN、NULLを含むとNOT INがはまる落とし穴まで、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 の行はどう扱われますか。