データベースには必ずNULLが混在します。「電話番号が未登録の顧客を洗い出したい」「必須項目が埋まっていないレコードを一括チェックしたい」「NULL件数が全体の何%かを把握したい」——こうしたデータ品質の確認・欠損値の発見・補完の場面でIS NULLは中心的な役割を果たします。
本記事ではIS NULLの基本から、NULL比較の落とし穴(三値論理)・複数列の欠損チェック・NULLの件数と割合を集計するデータ品質クエリ・COALESCE/NVL/IFNULLなどの補完関数・集計関数でのNULL動作・ORDER BYでのNULL順序制御・インデックスとNULLの関係まで網羅します。
NULLとは何か
NULLは「値が存在しない・不明・未入力」を表すSQL特有の状態です。空文字列 ''・数値ゼロ・false とはまったく別物です。
| 種類 | 意味 | IS NULL? | LENGTH() |
|---|---|---|---|
NULL |
値なし・不明 | TRUE | NULL |
''(空文字) |
長さ0の文字列 | FALSE | 0 |
0(数値ゼロ) |
ゼロという値 | FALSE | 1 |
'NULL'(文字列) |
“NULL”という4文字の文字列 | FALSE | 4 |
'' として格納されていることがあります。WHERE email IS NULL では空文字の行は取得できません。両方を検索するには WHERE email IS NULL OR email = '' と書く必要があります。IS NULLの基本構文
-- NULLの行を検索(IS NULL) SELECT * FROM users WHERE email IS NULL; -- NULL以外の行を検索(IS NOT NULL) SELECT * FROM users WHERE email IS NOT NULL; -- 複数条件: emailまたはphoneのどちらかがNULL SELECT * FROM users WHERE email IS NULL OR phone IS NULL; -- 複数条件: emailとphoneの両方がNULL SELECT * FROM users WHERE email IS NULL AND phone IS NULL;
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100), -- NULL許可
phone VARCHAR(20), -- NULL許可
age INT, -- NULL許可
dept_id INT -- NULL許可
);
INSERT INTO customers VALUES
(1, '田中', 'tanaka@example.com', '090-1111-2222', 28, 10),
(2, '佐藤', NULL, '080-3333-4444', 35, 10),
(3, '鈴木', 'suzuki@example.com', NULL, 42, 20),
(4, '高橋', NULL, NULL, NULL, 20),
(5, '伊藤', 'ito@example.com', '070-5555-6666', 55, NULL),
(6, '渡辺', NULL, '090-7777-8888', 31, 30),
(7, '山本', '', NULL, 23, 30); -- emailは空文字
= NULLが動かない理由:三値論理
SQLの条件評価は TRUE / FALSE / UNKNOWN の3値で行われます(三値論理)。NULLとの比較は常にUNKNOWNになり、WHERE句ではUNKNOWNはFALSEとして扱われます。そのためNULLを検索するには IS NULL という専用の構文が必要です。
| 式 | 値が100の場合 | 値がNULLの場合 | 行は返る? |
|---|---|---|---|
col = NULL |
FALSE | UNKNOWN | 返らない(常に0件) |
col != NULL |
TRUE | UNKNOWN | NULLの行は返らない |
col = 100 |
TRUE | UNKNOWN | NULLの行は返らない |
col IS NULL |
FALSE | TRUE | NULLの行だけ返る |
col IS NOT NULL |
TRUE | FALSE | NULLの行は返らない |
-- NG: 0件しか返らない(= NULL は常にUNKNOWN) SELECT * FROM customers WHERE email = NULL; -- 0件 SELECT * FROM customers WHERE email != NULL; -- 0件 -- OK: IS NULL を使う SELECT * FROM customers WHERE email IS NULL; -- → 佐藤(id=2), 高橋(id=4), 渡辺(id=6) が返る(山本の空文字は返らない) -- 空文字とNULLを両方取得したい場合 SELECT * FROM customers WHERE email IS NULL OR email = ''; -- → 佐藤, 高橋, 渡辺, 山本 が返る
NULL = NULL は TRUE ではなく UNKNOWN です。「NULLかどうか」を判定するには必ず IS NULL を使ってください。これはすべてのDBMS(MySQL・PostgreSQL・Oracle・SQL Server)で共通の動作です。複数カラムの欠損チェックパターン
-- email または phone のどちらかが未登録の顧客 SELECT id, name, email, phone FROM customers WHERE email IS NULL OR phone IS NULL; -- → 佐藤(phone有), 鈴木(email有), 高橋(両方NULL), 渡辺(email NULL), 山本(phone NULL)
-- email と phone の両方が未登録(連絡不能な顧客) SELECT id, name FROM customers WHERE email IS NULL AND phone IS NULL; -- → 高橋(id=4) のみ
-- 各カラムのNULL件数と割合を一度に集計(データ品質チェック)
SELECT
COUNT(*) AS total_rows,
-- NULL件数(IS NULL で1、非NULLで0に変換してSUM)
SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) AS email_null_count,
SUM(CASE WHEN phone IS NULL THEN 1 ELSE 0 END) AS phone_null_count,
SUM(CASE WHEN age IS NULL THEN 1 ELSE 0 END) AS age_null_count,
SUM(CASE WHEN dept_id IS NULL THEN 1 ELSE 0 END) AS dept_null_count,
-- NULL割合(%)
ROUND(100.0 * SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) / COUNT(*), 1)
AS email_null_pct
FROM customers;
-- 各行に何個のNULLが含まれるかをカウント(欠損スコア)
SELECT
id,
name,
(CASE WHEN email IS NULL THEN 1 ELSE 0 END +
CASE WHEN phone IS NULL THEN 1 ELSE 0 END +
CASE WHEN age IS NULL THEN 1 ELSE 0 END +
CASE WHEN dept_id IS NULL THEN 1 ELSE 0 END) AS null_count
FROM customers
ORDER BY null_count DESC; -- NULL数が多い順
-- 高橋: null_count = 3(email・phone・ageがNULL)
NULLを別の値に置き換える関数の比較
検索や表示の際に NULL を「未登録」「0」などの値に置き換えたい場合は、DB種類に応じた補完関数を使います。
| 関数 | 構文 | 対応DBMS | 特徴 |
|---|---|---|---|
COALESCE |
COALESCE(a, b, c, ...) |
全DB(SQL標準) | 最初の非NULL値を返す。複数引数対応 |
IFNULL |
IFNULL(a, b) |
MySQL | aがNULLならb。引数は2つのみ |
NVL |
NVL(a, b) |
Oracle | aがNULLならb。引数は2つのみ |
ISNULL |
ISNULL(a, b) |
SQL Server | aがNULLならb。引数は2つのみ |
NULLIF |
NULLIF(a, b) |
全DB | a = b のときNULLを返す(逆方向) |
-- NULL を "未登録" に置き換えて表示
SELECT
id,
name,
COALESCE(email, '未登録') AS email_display,
COALESCE(phone, '未登録') AS phone_display,
COALESCE(age, 0) AS age_display -- NULLを0に
FROM customers;
-- 複数のフォールバック(最初の非NULLを使う)
SELECT COALESCE(email, phone, '連絡先なし') AS contact
FROM customers;
-- emailがNULLならphoneを、phoneもNULLなら'連絡先なし'を表示
-- 空文字をNULLに変換(空文字とNULLを統一して扱う場合)
SELECT
id,
name,
NULLIF(email, '') AS email_normalized -- '' → NULL に変換
FROM customers;
-- 山本の email '' が NULL になり、IS NULL で検索できるようになる
-- NULLIF + COALESCE の組み合わせ(空文字もNULLも同じ代替値に)
SELECT
COALESCE(NULLIF(email, ''), '未登録') AS email_display
FROM customers;
集計関数でのNULLの扱い:COUNT(*)とCOUNT(列)の違い
集計関数はNULLを特別に扱います。COUNT(*) はNULLを含むすべての行を数え、COUNT(列名) はNULLを無視します。SUM・AVG・MAX・MINもすべてNULLを無視します。
SELECT
COUNT(*) AS total_rows, -- 7(NULL含む全行)
COUNT(email) AS email_count, -- 3(NULLを無視: 田中・鈴木・伊藤 + 空文字の山本=4?)
-- ※ COUNT(email)は空文字''もカウントする。NULLのみを除外
COUNT(age) AS age_count, -- 6(高橋のNULLを除く)
COUNT(DISTINCT dept_id) AS dept_count, -- 3(NULL除く、かつ重複除く)
SUM(age) AS sum_age, -- NULL は無視して合計
AVG(age) AS avg_age, -- NULL行を除いた平均(高橋の NULL は除外)
MAX(age) AS max_age, -- NULL は無視
MIN(age) AS min_age -- NULL は無視
FROM customers;
SUM(COALESCE(age, 0)) / COUNT(*) で計算してください。単純な AVG(age) とは結果が変わります。-- NULLを0として扱った平均(NULL行を0点として含める)
SELECT
AVG(age) AS avg_ignoring_null, -- NULL除外の平均
SUM(COALESCE(age, 0)) / COUNT(*) AS avg_treating_null_as_zero -- NULL=0の平均
FROM customers;
-- NULL行数と非NULL行数を同時にカウント
SELECT
COUNT(*) AS total,
COUNT(email) AS not_null_count,
COUNT(*) - COUNT(email) AS null_count,
ROUND(100.0 * (COUNT(*) - COUNT(email)) / COUNT(*), 1) AS null_pct
FROM customers;
ORDER BY でのNULL順序制御(NULLS FIRST / NULLS LAST)
ORDER BY でNULLをどの位置に並べるかはDBMSによって異なります。明示的に制御したい場合は NULLS FIRST / NULLS LAST を使います。
| DBMS | ASCのデフォルト | DESCのデフォルト | NULLS FIRST/LAST 対応 |
|---|---|---|---|
| MySQL | NULLが先頭 | NULLが末尾 | × 非対応(CASE WHENで代替) |
| PostgreSQL | NULLが末尾 | NULLが先頭 | ○ NULLS FIRST/LAST対応 |
| Oracle | NULLが末尾 | NULLが先頭 | ○ NULLS FIRST/LAST対応 |
| SQL Server | NULLが先頭 | NULLが先頭 | × 非対応(CASE WHENで代替) |
-- NULLを末尾に(ASCでNULLを最後に表示) SELECT id, name, age FROM customers ORDER BY age ASC NULLS LAST; -- PostgreSQL / Oracle -- NULLを先頭に(ASCでNULLを最初に表示) SELECT id, name, age FROM customers ORDER BY age ASC NULLS FIRST; -- PostgreSQL / Oracle
-- MySQL: NULLを末尾に並べる(CASE WHENで代替)
SELECT id, name, age FROM customers
ORDER BY
CASE WHEN age IS NULL THEN 1 ELSE 0 END, -- NULLは1(後ろに)
age ASC;
-- SQL Server: NULLを末尾に並べる
SELECT id, name, age FROM customers
ORDER BY
CASE WHEN age IS NULL THEN 1 ELSE 0 END,
age ASC;
-- MySQL: COALESCE で大きい値に置き換えてNULLを末尾に
SELECT id, name, age FROM customers
ORDER BY COALESCE(age, 9999) ASC; -- NULLを9999として末尾へ
JOINとNULL:OUTER JOINで発生するNULLを検索する
OUTER JOINでは結合できなかった側の列がNULLになります。このNULLをIS NULLで絞り込むことで「片方のテーブルにしか存在しないデータ」を取得できます。
-- ordersに購入記録がない顧客(未購入者)を抽出 SELECT c.id, c.name FROM customers c LEFT JOIN orders o ON c.id = o.customer_id WHERE o.id IS NULL; -- 結合できなかった = purchaseなし -- NULLをもたらす仕組み: -- LEFT JOINは左テーブルの全行を保持 -- 右テーブルに一致行がなければ右側列はすべてNULLになる -- → WHERE 右テーブル列 IS NULL で「一致しなかった行」だけを残す
o.id)でIS NULLを判定してください。データ列(例: o.amount)でIS NULLを判定すると、金額がNULLのレコードも拾ってしまいます。インデックスとNULLの関係
NULLのデータにインデックスが効くかどうかはDBMSによって異なります。大量のNULL行を含むカラムに対してIS NULL検索をする場合、パフォーマンスに影響します。
| DBMS | BツリーインデックスにNULLを含む? | IS NULL検索でインデックス使用? |
|---|---|---|
| MySQL(InnoDB) | 含む | ○ 使える |
| PostgreSQL | 含む | ○ 使える |
| Oracle | 含まない(デフォルト) | × Bツリーでは使えない(ビットマップ索引は可) |
| SQL Server | 含む | ○ 使える |
WHERE col IS NULL ではBツリーインデックスが使われず、全件スキャンになります。大量のNULL行を含むカラムに対してIS NULL検索が頻繁に発生する場合は、ビットマップインデックスの使用や、NOT NULLデフォルト値の設計を検討してください。-- Oracle: IS NULL をインデックス利用できる形に書き換える -- (NULLを特定の番哨値に変換した関数インデックスを使う方法) CREATE INDEX idx_email_null ON customers (NVL(email, 'NULL_SENTINEL')); -- → WHERE NVL(email, 'NULL_SENTINEL') = 'NULL_SENTINEL' で高速検索可能 -- MySQL / PostgreSQL は IS NULL でインデックスが効く EXPLAIN SELECT * FROM customers WHERE email IS NULL; -- type: ref(インデックス使用)
実務でよく使うIS NULLパターン集
未処理レコードの抽出(バッチ処理用)
-- 未処理レコードを取得(processed_at が NULL = まだ処理していない) SELECT id, data, created_at FROM jobs WHERE processed_at IS NULL ORDER BY created_at LIMIT 100; -- MySQL: 一度に100件ずつ処理 -- 処理完了後にマーク UPDATE jobs SET processed_at = NOW() WHERE id IN (/* 処理したIDのリスト */);
退職者・退会者を除いた在籍中のみ取得
-- resigned_at がNULL = まだ在籍中 SELECT employee_id, name, email FROM employees WHERE resigned_at IS NULL; -- NULLでない = 退職済み SELECT employee_id, name, resigned_at FROM employees WHERE resigned_at IS NOT NULL;
必須項目の未入力レコードを洗い出す(データクレンジング)
-- いずれかの必須項目が未入力の顧客を洗い出す
SELECT
id,
name,
CASE WHEN email IS NULL THEN '○' ELSE '' END AS email_missing,
CASE WHEN phone IS NULL THEN '○' ELSE '' END AS phone_missing,
CASE WHEN age IS NULL THEN '○' ELSE '' END AS age_missing,
CASE WHEN dept_id IS NULL THEN '○' ELSE '' END AS dept_missing
FROM customers
WHERE email IS NULL
OR phone IS NULL
OR age IS NULL
ORDER BY id;
NULL件数のカラム横断モニタリングクエリ
-- UNION ALL でカラム別のNULL件数を縦持ちで出力(任意のカラム数に対応) SELECT 'email' AS column_name, COUNT(*) AS null_count FROM customers WHERE email IS NULL UNION ALL SELECT 'phone' AS column_name, COUNT(*) AS null_count FROM customers WHERE phone IS NULL UNION ALL SELECT 'age' AS column_name, COUNT(*) AS null_count FROM customers WHERE age IS NULL UNION ALL SELECT 'dept_id' AS column_name, COUNT(*) AS null_count FROM customers WHERE dept_id IS NULL ORDER BY null_count DESC;
よくある質問
= NULL では何も返りません。NULLの検索には必ず IS NULL を使ってください。WHERE col IS NULL OR col = '' とORで組み合わせます。あるいは WHERE NULLIF(col, '') IS NULL とNULLIF関数を使う方法もあります。NULLIF(a, b) は a = b のときNULLを返すため、空文字もNULLに統一して判定できます。COALESCE(col, '未登録') を使います。MySQL なら IFNULL(col, '未登録')、Oracle なら NVL(col, '未登録')、SQL Server なら ISNULL(col, '未登録') でも同様の結果が得られます。全DBで動く書き方にするなら COALESCE が最も移植性が高くおすすめです。ORDER BY col ASC NULLS LAST で末尾に送れます。MySQL・SQL Serverは ORDER BY CASE WHEN col IS NULL THEN 1 ELSE 0 END, col ASC で同じ効果が得られます。まとめ
NULLの検索はデータベース操作の基本ですが、三値論理・集計関数の挙動・インデックスとの関係など知っておくべきポイントが多くあります。
- IS NULL / IS NOT NULL: NULLの判定には専用演算子を使う。
= NULLは常に0件になる - 三値論理: NULLとの比較はTRUE/FALSEではなくUNKNOWN。WHERE句ではFALSEと同様に除外される
- 空文字との区別:
IS NULL OR col = ''で両方をまとめて検索。NULLIF で統一も可 - 欠損集計:
SUM(CASE WHEN col IS NULL THEN 1 ELSE 0 END)でNULL件数・割合を集計できる - 補完関数: COALESCE(全DB)/ IFNULL(MySQL)/ NVL(Oracle)/ ISNULL(SQL Server)で代替値を設定
- 集計関数: COUNT(*)は全件、COUNT(列)はNULL除外。SUM/AVG/MAX/MINもNULLを無視する
- ORDER BY: NULLS FIRST/LAST(Oracle・PostgreSQL)またはCASE WHENで順序制御
- OracleのIS NULL検索: BツリーインデックスにNULLが含まれないため全件スキャンに注意
関連記事:IS NOT NULL完全ガイド(三値論理・JOIN・GROUP BY・パフォーマンス)、NULLを含むカラムでの条件分岐・フィルターの書き方、UPDATE文でNULLに更新する方法、CASE WHEN完全ガイド
