NULLを正しく扱う
NULL とは何か
NULL? SQLにおける「値が存在しない」を表す特殊な状態。NULLは値ではないため、`= NULL` では比較できず、`IS NULL` / `IS NOT NULL` を使う。NULLを含む演算は基本的にNULLになる。 は「値が存在しない」「不明」「適用不可」を表す特別な状態です。これは 0(数値のゼロ)や ''(空文字列)とは根本的に異なります。
-- NULLとゼロと空文字の違い
NULL -- 値が存在しない(例: 電話番号が登録されていない)
0 -- 数値の0(例: 残高が0円)
'' -- 空の文字列(例: コメントが空欄で提出された)
この違いを理解していないと、検索や集計で意図しない結果が返ることがあります。
3値論理(TRUE / FALSE / UNKNOWN)
通常のプログラミング言語の論理値はTRUEかFALSEの2値です。しかしSQLでは NULL を含む比較の結果は UNKNOWN になります。これが「3値論理」です。
NULLとの比較は常にUNKNOWN
NULL = NULL -- UNKNOWN(NULLはNULLと等しくない!)
NULL = 1 -- UNKNOWN
NULL <> 1 -- UNKNOWN
NULL > 1 -- UNKNOWN
NULL + 1 -- NULL(算術演算もNULL)
NULL || 'abc' -- NULL(文字列結合もNULL)
AND・OR の3値論理表
| A | B | A AND B | A OR B |
|---|---|---|---|
| TRUE | UNKNOWN | UNKNOWN | TRUE |
| FALSE | UNKNOWN | FALSE | UNKNOWN |
| UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN |
WHEREの条件がUNKNOWNになった行はFILTERされる(結果に含まれない)ことを覚えておきましょう。
IS NULL / IS NOT NULL
NULLかどうかを判定するには IS NULL / IS NOT NULL を使います。= NULL は使えません。
-- 誤り: NULLを = で比較する(常にUNKNOWNになり0行返る)
SELECT * FROM customers WHERE phone = NULL; -- 0行返る(意図どおりではない)
-- 正しい: IS NULLを使う
SELECT * FROM customers WHERE phone IS NULL; -- NULLの行が返る
-- NOT NULLを確認
SELECT * FROM customers WHERE phone IS NOT NULL;
COALESCE() と NULLIF()
COALESCE()
COALESCE(値1, 値2, 値3, ...) は最初のNULLでない値を返します。デフォルト値を指定するときに便利です。
-- NULLの場合に '未登録' を表示
SELECT
name,
COALESCE(phone, '未登録') AS phone_display
FROM customers;
-- 複数のフォールバックを設定
SELECT COALESCE(nickname, first_name, '名無し') AS display_name
FROM users;
NULLIF()
NULLIF(値1, 値2) は2つの値が等しい場合にNULLを返します。ゼロ除算を防ぐのに役立ちます。
-- ゼロ除算を防ぐ
SELECT
total_revenue,
total_orders,
total_revenue / NULLIF(total_orders, 0) AS avg_order_value
-- total_orders が 0 の場合はNULLを返し、ゼロ除算エラーを回避
FROM sales_summary;
COUNT(*) vs COUNT(col)
NULLの扱いで結果が変わる代表例が COUNT です。
-- customersテーブル
-- id | name | email
-- 1 | 田中 | tanaka@example.com
-- 2 | 佐藤 | NULL ← emailがNULL
-- 3 | 鈴木 | suzuki@example.com
SELECT
COUNT(*) AS all_count, -- 3(全行をカウント、NULLも含む)
COUNT(email) AS email_count, -- 2(NULLを除くemailがある行のみ)
COUNT(DISTINCT email) AS unique_email -- 2(NULLは除かれユニーク数)
FROM customers;
COUNT(*) は行数そのもの、COUNT(col) はその列がNULLでない行数です。意図に合わせて使い分けてください。
集約関数とNULL
SUM、AVG、MAX、MIN など他の集約関数もNULLを無視します。
-- scoresテーブル: 10, 20, NULL, 30
SELECT
SUM(score), -- 60(NULLは無視)
AVG(score), -- 20(60 / 3 ← NULLは分母にも含まれない)
COUNT(*), -- 4(NULLも含む行数)
COUNT(score) -- 3(NULLを除く)
FROM scores;
NOT IN にNULLが含まれる罠
NOT IN のリストにNULLが1つでも含まれると、結果が0行になるという罠があります。
-- manager_idテーブル
-- id: 1, 2, 3, 4(4はNULL)
SELECT *
FROM employees
WHERE id NOT IN (1, 2, NULL);
-- 結果: 0行!(意図は id が 1 でも 2 でもない行)
なぜか? NOT IN は <> 1 AND <> 2 AND <> NULL に展開されます。id <> NULL は UNKNOWN になり、TRUE AND UNKNOWN は UNKNOWN になるため、すべての行がフィルタされてしまいます。
-- サブクエリにNULLが含まれるケース(よくある)
SELECT *
FROM employees
WHERE department_id NOT IN (
SELECT department_id FROM departments WHERE active = false
-- department_id に NULL が含まれると全件除外される
);
-- 解決策1: IS NOT NULLで除外
SELECT *
FROM employees
WHERE department_id NOT IN (
SELECT department_id FROM departments
WHERE active = false AND department_id IS NOT NULL
);
-- 解決策2: NOT EXISTSを使う(NULLの罠がない)
SELECT *
FROM employees e
WHERE NOT EXISTS (
SELECT 1 FROM departments d
WHERE d.department_id = e.department_id
AND d.active = false
);
JOIN とNULL
LEFT JOIN でNULLが生成されます。NULLに対して条件を設定するときは注意が必要です。
-- 注文していない顧客(LEFT JOINの活用)
SELECT c.name
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.order_id IS NULL; -- JOINで一致しなかった行はorder_idがNULL
-- 間違い: NULL は = NULL で比較できない
WHERE o.order_id = NULL -- 常にUNKNOWN → 0行返る
まとめ
- NULLは「値がない」状態で、0や空文字とは別物
- NULLを含む比較は常にUNKNOWN(TRUEでもFALSEでもない)
- NULLの判定には
= NULLではなくIS NULL/IS NOT NULLを使う COALESCE()でデフォルト値を指定、NULLIF()でゼロ除算を防ぐCOUNT(*)は全行、COUNT(col)はNULLを除いた行をカウントNOT INのリストにNULLが含まれると全件が除外される罠に注意
次回は トランザクション? 複数のDB操作をひとまとまりとして扱う仕組み。途中でエラーが起きたら全部元に戻す(ロールバック)ことで、データの整合性を保つ。 と ACID特性? トランザクションが満たすべき4つの性質。Atomicity(原子性・全部成功か全部失敗)・Consistency(一貫性)・Isolation(分離性・他のトランザクションの影響を受けない)・Durability(永続性)。 特性について解説します。