コマンド道場

IS NULL / IS NOT NULL の使い方

ひとことで言うと

shain社員

namebusho_id
大野NULL
渡辺NULL
中村2
井上7

IS NULL値が無い行だけ

namebusho_id
大野NULL
渡辺NULL

WHERE busho_id IS NULL

  • 取り消し線= 条件に合わないので結果に出てこない行
  • 社員 18 行 → 値が無い行だけ 216 行が消えます)

`busho_id = NULL` と書くと、この 2 行すら返りません(0 行になります)。

`NULL` は「値が無い」ではなく「値が分からない」という意味です。 分からないものを = で比べても答えは出ません。だから `= NULL` は動かず、`IS NULL` を使います。この一点を押さえると、NULL 絡みの事故はほぼ防げます。

主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2

製品ごとの対応表(実測)を見る

このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。

01真・偽のほかに「どちらとも言えない」がある

SQL の条件は、真・偽の 2 つではなく 真・偽・unknown(どちらとも言えない)の 3 つになります。これを三値論理といいます。

NULL が絡む比較は、ほぼすべて unknown になります。NULL = 1NULL <> 1NULL = NULL も、答えは unknown です。「分からない値」と何かを比べても、分からないままだからです。

`WHERE` は真になった行だけを残します。 unknown の行は残りません。ここが事故の源です。

1 行ずつ、何をしているか
  1. SELECT name, busho_id何を出すか
  2. FROM shainどの表を見るか
  3. WHERE busho_id IS NULL値が入っていない行だけ残す
  4. ORDER BY shain_id並び順

「値が入っている行」なら IS NOT NULL。書き方はこの 2 つだけで、= NULL は使いません(エラーにならず黙って 0 行を返すので、間違いに気づけません)。

比べた結果そのものを見る
SELECT
  (NULL = NULL)   AS null_eq_null,
  (NULL <> 1)     AS null_ne_1,
  (NULL IS NULL)  AS null_is_null;
null_eq_nullnull_ne_1null_is_null
NULLNULLtrue

1

はじめの 2 つは true でも false でもなく、空欄(unknown)です。IS NULL だけが true を返します。

02⚠️ よくある間違い:NOT IN は NULL が 1 つ混ざると全滅する

これは NULL 絡みで最も痛い罠です。

x NOT IN (1, 2, NULL) は「x は 1 でもなく 2 でもなく NULL でもない」という意味です。ところが「x が NULL ではない」は unknown なので、全体が真になりません。結果は常に 0 行です。

副問い合わせの結果に NULL が 1 つでも混ざっていると、これが起こります。件数が 0 になって初めて気づく、という形で表面化します。

対処は 2 つあります。NOT EXISTS に書き換えるか、副問い合わせ側で IS NOT NULL を足すかです。NOT EXISTS のほうが安全なので、迷ったらこちらを使ってください。

0 行になってしまう例
SELECT name
FROM shain
WHERE busho_id NOT IN (SELECT busho_id FROM shain WHERE taishoku_on IS NOT NULL);
name
鈴木
佐藤
田中
中村
小林
加藤
吉田
井上
木村

9

「退職者がいる部署以外」を出したかったのに 0 行です。副問い合わせの結果に NULL が混ざっているためです。

NOT EXISTS なら NULL に振り回されない
SELECT s.name
FROM shain s
WHERE NOT EXISTS (
  SELECT 1 FROM shain t
  WHERE t.taishoku_on IS NOT NULL AND t.busho_id = s.busho_id
)
ORDER BY s.shain_id;
name
鈴木
佐藤
田中
中村
小林
加藤

11 … うち先頭 6 行を表示

03並べ替えると NULL は端に寄る

ORDER BY では NULL を「いちばん大きい値」として扱うか「いちばん小さい値」として扱うかが製品によって違います

PostgreSQL は昇順のとき NULL を最後に置きます。位置を自分で決めたいときは `NULLS FIRST` / `NULLS LAST` と書きます。

ただしこの書き方に対応していない製品があります。どこでも動かしたいなら、CASE で「NULL なら 1、それ以外は 0」という列を作って並べる方法があります。

NULL を先頭に持ってくる
SELECT name, busho_id
FROM shain
ORDER BY busho_id NULLS FIRST, shain_id;
namebusho_id
渡辺NULL
大野NULL
鈴木1
佐藤2
田中2

18 … うち先頭 5 行を表示

⚠️ この書き方は MySQL / MariaDB / SQL Server では使えません(ORDER BY ... NULLS LAST)。製品ごとの対応表

04COALESCE で「代わりの値」に置き換える

画面に出すときは、NULL のままだと空欄になって意味が伝わりません。`COALESCE(列, 代わりの値)` を使うと、NULL のときだけ別の値にできます。

引数は 3 つ以上書けます。左から順に見て、最初に `NULL` でなかったものが返ります。

⚠️ 置き換えるのは表示のときだけにしてください。集計の途中で COALESCE(salary, 0) のように 0 を入れると、平均が実際より低く出ます(次の節)。

退職日が無い人を「在籍中」と表示する
SELECT name, COALESCE(taishoku_on::text, '在籍中') AS jotai
FROM shain
ORDER BY shain_id;
namejotai
鈴木在籍中
佐藤在籍中
田中在籍中
中村在籍中
小林在籍中

18 … うち先頭 5 行を表示

05⚠️ よくある間違い:集約関数は NULL を数えない

count(列) sum() avg() は、`NULL` の行を最初から無かったものとして扱います

つまり avg(列) の分母は「値が入っている行の数」であって、全体の行数ではありません。ここを勘違いすると、平均が想定と食い違います。

NULL を 0 とみなして平均したい」なら、COALESCE で明示的に 0 にしてから平均します。どちらが正しいかは要件次第なので、無意識に決めないでください。

行数そのものを数えたいときは count(*) です。こちらは NULL に関係なく行を数えます。

行数・値が入っている数・平均を並べる
SELECT
  count(*)                        AS zen_gyosu,
  count(taishoku_on)              AS chi_ari,
  avg(salary)::numeric(10,0)      AS heikin
FROM shain;
zen_gyosuchi_ariheikin
182542003

1

count(*) と count(taishoku_on) の差が「NULL の行数」です。

自分で打ってみる

このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。

押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。

解いてみる

読んだだけでは書けるようになりません。実行結果で採点します。

関連するトピック

根拠(一次情報)