【SQL】IS NULLでNULLの項目を検索する完全ガイド|三値論理・複数列欠損チェック・COALESCE・集計・ORDER BY NULLS・インデックスまで解説

データベースには必ずNULLが混在します。「電話番号が未登録の顧客を洗い出したい」「必須項目が埋まっていないレコードを一括チェックしたい」「NULL件数が全体の何%かを把握したい」——こうしたデータ品質の確認・欠損値の発見・補完の場面でIS NULLは中心的な役割を果たします。

本記事ではIS NULLの基本から、NULL比較の落とし穴(三値論理)・複数列の欠損チェック・NULLの件数と割合を集計するデータ品質クエリ・COALESCE/NVL/IFNULLなどの補完関数・集計関数でのNULL動作・ORDER BYでのNULL順序制御・インデックスとNULLの関係まで網羅します。

この記事で学べること:IS NULL / IS NOT NULLの基本・= NULLが動かない理由(三値論理)・複数カラムの欠損チェック・NULL件数と割合の集計クエリ・COALESCE/NVL/IFNULL/ISNULLの使い分け・COUNT(*)とCOUNT(列)の違い・ORDER BY NULLS FIRST/LAST・インデックスとNULLの注意点
スポンサーリンク

NULLとは何か

NULLは「値が存在しない・不明・未入力」を表すSQL特有の状態です。空文字列 ''・数値ゼロ・false とはまったく別物です。

種類 意味 IS NULL? LENGTH()
NULL 値なし・不明 TRUE NULL
''(空文字) 長さ0の文字列 FALSE 0
0(数値ゼロ) ゼロという値 FALSE 1
'NULL'(文字列) “NULL”という4文字の文字列 FALSE 4
空文字とNULLの混同に注意:フォームから空のまま送信されたデータがNULLではなく空文字 '' として格納されていることがあります。WHERE email IS NULL では空文字の行は取得できません。両方を検索するには WHERE email IS NULL OR email = '' と書く必要があります。

IS NULLの基本構文

IS NULL / IS NOT 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の行は返らない
= NULLとIS 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にならない:SQLでは NULL = NULL は TRUE ではなく UNKNOWN です。「NULLかどうか」を判定するには必ず IS NULL を使ってください。これはすべてのDBMS(MySQL・PostgreSQL・Oracle・SQL Server)で共通の動作です。

複数カラムの欠損チェックパターン

いずれかのカラムがNULLの行を検索
-- email または phone のどちらかが未登録の顧客
SELECT id, name, email, phone
FROM   customers
WHERE  email IS NULL OR phone IS NULL;
-- → 佐藤(phone有), 鈴木(email有), 高橋(両方NULL), 渡辺(email NULL), 山本(phone NULL)
すべてのカラムがNULLの行を検索
-- email と phone の両方が未登録(連絡不能な顧客)
SELECT id, name
FROM   customers
WHERE  email IS NULL AND phone IS NULL;
-- → 高橋(id=4) のみ
NULLカラム数を列ごとに集計(欠損率チェック)
-- 各カラムの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;
1行に何個のNULL列があるかを集計(欠損スコア)
-- 各行に何個の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を別の値に置き換える関数の比較

検索や表示の際に 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を返す(逆方向)
COALESCE(全DB共通・推奨)
-- 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なら'連絡先なし'を表示
NULLIF(空文字を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を無視します。

集計関数と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;
AVGのNULL罠:AVG(age) はNULLを除いた行の平均です。「NULLを0として扱った平均」が欲しい場合は SUM(COALESCE(age, 0)) / COUNT(*) で計算してください。単純な AVG(age) とは結果が変わります。
NULL を 0 として扱う集計
-- 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で代替)
NULLS FIRST / NULLS LAST(PostgreSQL・Oracle)
-- 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とSQL ServerでのNULL順序制御(CASE WHENで代替)
-- 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で絞り込むことで「片方のテーブルにしか存在しないデータ」を取得できます。

LEFT JOIN + 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 で「一致しなかった行」だけを残す
JOIN後のNULLと元データのNULLを区別する:OUTER JOIN後には「元々NULLだった値」と「結合に失敗して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 含む ○ 使える
OracleのIS NULL検索は注意:OracleのBツリーインデックスはデフォルトでNULL値を含みません。そのため WHERE col IS NULL ではBツリーインデックスが使われず、全件スキャンになります。大量のNULL行を含むカラムに対してIS NULL検索が頻繁に発生する場合は、ビットマップインデックスの使用や、NOT NULLデフォルト値の設計を検討してください。
Oracle: IS 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の未処理行を抽出してバッチ処理
-- 未処理レコードを取得(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のリスト */);

退職者・退会者を除いた在籍中のみ取得

退職日・退会日がNULLの在籍中のみ取得
-- 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件数のカラム横断モニタリングクエリ

全カラムのNULL件数を縦持ちで取得(UNION ALL)
-- 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;

よくある質問

QWHERE col = NULL と書いたのに結果が0件になります
ASQL の NULL との比較は常に UNKNOWN(= FALSE 扱い)になるため、= NULL では何も返りません。NULLの検索には必ず IS NULL を使ってください。
Q空文字(”)とNULLを両方まとめて検索するには?
AWHERE col IS NULL OR col = '' とORで組み合わせます。あるいは WHERE NULLIF(col, '') IS NULL とNULLIF関数を使う方法もあります。NULLIF(a, b) は a = b のときNULLを返すため、空文字もNULLに統一して判定できます。
QNULLをゼロや「未登録」に置き換えて表示するには?
ACOALESCE(col, '未登録') を使います。MySQL なら IFNULL(col, '未登録')、Oracle なら NVL(col, '未登録')、SQL Server なら ISNULL(col, '未登録') でも同様の結果が得られます。全DBで動く書き方にするなら COALESCE が最も移植性が高くおすすめです。
QORDER BYでNULLを必ず末尾に表示するには?
APostgreSQL・Oracleは ORDER BY col ASC NULLS LAST で末尾に送れます。MySQL・SQL Serverは ORDER BY CASE WHEN col IS NULL THEN 1 ELSE 0 END, col ASC で同じ効果が得られます。
QCOUNT(*) と COUNT(列名) の結果が違うのはなぜですか?
ACOUNT(*) は NULL を含む全行を数えます。COUNT(列名) は対象列が NULL の行を除外してカウントします。例えば email が NULL の行が3件あれば、COUNT(*) – COUNT(email) = 3 となります。「この列が何件入力済みか」を知りたいときは COUNT(列名)、「全件数」を知りたいときは COUNT(*) を使います。

まとめ

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が含まれないため全件スキャンに注意
NULL検索の使い方まとめ:欠損値を見つける → IS NULL、存在する値のみ対象 → IS NOT NULL、NULLを特定値で置き換え → COALESCE、NULL件数を集計 → COUNT(*) – COUNT(列名) または SUM(CASE WHEN IS NULL)

関連記事:IS NOT NULL完全ガイド(三値論理・JOIN・GROUP BY・パフォーマンス)NULLを含むカラムでの条件分岐・フィルターの書き方UPDATE文でNULLに更新する方法CASE WHEN完全ガイド