SQL で一番つまずきやすい値のひとつが NULL(不明・未設定)です。練習用の members を作り、比較、COUNT、COALESCE、CASE をまとめて確認します。
目次
NULL 入りの表
cd $DB_SQL/PostgreSQL_setupdocker compose exec db psql -U practice -d practice_dbDROP TABLE IF EXISTS members;CREATE TABLE members ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, nickname TEXT, points INT);INSERT INTO members (name, nickname, points) VALUES ('山田', 'ヤマ', 10), ('佐藤', NULL, 0), ('鈴木', 'スズ', NULL), ('高橋', NULL, NULL);比較と IS NULL
これはニックネームが無い行を返しません(NULL = NULL は真にならないため)。よくあるのは = NULL で絞ろうとして空になり、原因に気づくまで時間がかかることです。
SELECT * FROM members WHERE nickname = NULL; -- 結果は常に空正しい書き方:
SELECT * FROM members WHERE nickname IS NULL;SELECT * FROM members WHERE nickname IS NOT NULL;nickname IS NULL なら佐藤・高橋の 2 行、IS NOT NULL なら山田・鈴木の 2 行です。
points = 0 と points IS NULL は別物です。0 は「点がゼロ」、NULL は「まだ無い」です。
SELECT name, points FROM members WHERE points = 0;SELECT name, points FROM members WHERE points IS NULL;COALESCE と NULLIF
COALESCE は左から見て最初の非 NULL を返します。表示用のフォールバックに便利です。
SELECT name, COALESCE(nickname, name) AS display_name, COALESCE(points, 0) AS points_or_zeroFROM members;NULLIF(a, b) は a = b のとき NULL、それ以外は a です。ゼロ除算回避などにも使います。
SELECT NULLIF(points, 0) FROM members;CASE
SELECT name, points, CASE WHEN points IS NULL THEN '未設定' WHEN points = 0 THEN 'ゼロ' WHEN points >= 10 THEN '高め' ELSE '普通' END AS points_labelFROM members;単純 CASE(一致比較):
SELECT name, CASE COALESCE(nickname, '') WHEN '' THEN 'ニックネームなし' ELSE nickname END AS nick_labelFROM members;集計での NULL
SELECT COUNT(*) AS all_rows, COUNT(nickname) AS has_nickname, COUNT(points) AS has_points, SUM(points) AS sum_points, AVG(points) AS avg_pointsFROM members;COUNT(*)… 行数(NULL 行も含む)COUNT(列)… その列が非 NULL の件数SUM/AVG… NULL は無視(ゼロとは数えない)
AVG(COALESCE(points, 0)) にすると「未設定を 0 とみなす平均」になり、意味が変わります。どちらが欲しいかを先に決めてください。
よくあるつまずき
| 症状 | 対処 |
|---|---|
= NULL で絞れない |
IS NULL を使う |
NOT IN (サブクエリ) がおかしい |
サブクエリ側の NULL |
| 平均が思ったより大きい/小さい | NULL を無視しているか、0 埋めしているか確認 |
| 文字列連結で全体が NULL | COALESCE で空文字に落とす |
掃除:
DROP TABLE IF EXISTS members;IS NULL と COUNT(列) の振る舞いが分かれば、欠損まわりの大半は説明できます。