Oracle の NULL は「値が存在しない」ことを表す特殊な状態です。NULL は数値 0 でも空文字列でもなく、あらゆる比較演算が UNKNOWN(真でも偽でもない第3の状態)になります。この挙動を理解せずに SQL を書くと、データが意図せず除外される不具合が発生します。
本記事では Oracle で利用できる NULL 処理関数(NVL・NVL2・COALESCE・NULLIF・LNNVL)の仕様と使い分けを、実際によく発生するトラブルパターンとともに解説します。
- Oracle における NULL の性質(3値論理・比較演算の挙動)
- NVL / NVL2 / COALESCE / NULLIF の違いと適切な使い分け
- DECODE での NULL 処理
- LNNVL:WHERE 句で NULL を含む条件を正しく扱う方法
- NOT IN に NULL が含まれると全件除外される落とし穴
- NULL を含む集計(COUNT・SUM・AVG)の挙動
- NULL 安全な結合・ソート・グループ化のパターン
- Oracle における NULL の基本性質
- NULL 処理関数の一覧と使い分け早見表
- NVL:NULL を別の値に置き換える基本関数
- NVL2:NULL か否かで異なる値を返す
- COALESCE:複数の候補から最初の非 NULL を返す(SQL 標準・推奨)
- NULLIF:特定の値を NULL に変換する
- LNNVL:NULL を含む条件を正しく扱う WHERE 句専用関数
- NOT IN に NULL が含まれると全件除外される落とし穴
- NULL を含む集計関数の挙動
- DECODE での NULL 処理
- ORDER BY と NULL:NULLS FIRST / NULLS LAST
- NULL 処理関数の使い分けまとめ
- まとめ
Oracle における NULL の基本性質
Oracle の NULL は SQL 標準に基づいた3値論理(TRUE / FALSE / UNKNOWN)に従います。
| 式 | 結果 | 理由 |
|---|---|---|
NULL = NULL |
UNKNOWN | NULL は「不明な値」どうしの比較も不明 |
NULL != NULL |
UNKNOWN | 同上 |
NULL = 0 |
UNKNOWN | NULL と任意の値の比較は UNKNOWN |
NULL > 0 |
UNKNOWN | 同上 |
NULL IS NULL |
TRUE | IS NULL は NULL チェック専用の構文 |
NULL IS NOT NULL |
FALSE | 同上 |
1 + NULL |
NULL | NULL を含む算術演算は常に NULL |
'abc' || NULL |
‘abc’ | Oracle独自仕様:文字列連結では NULL を空文字列として扱う |
-- WHERE 条件が UNKNOWN になると行が返らない SELECT * FROM employees WHERE manager_id = NULL; -- 0行(NULL=NULLはUNKNOWN) SELECT * FROM employees WHERE manager_id IS NULL; -- NULL行が返る(正しい書き方) -- NULL を含む算術演算 SELECT 100 + NULL FROM dual; -- NULL SELECT 100 - NULL FROM dual; -- NULL SELECT 100 * NULL FROM dual; -- NULL SELECT 100 / NULL FROM dual; -- NULL -- 文字列連結の Oracle 独自挙動(他 RDBMS と異なる) SELECT 'Hello' || NULL || ' World' FROM dual; -- 'Hello World'(NULLが無視される) -- ※ PostgreSQL/MySQL では NULL を含む連結は NULL になる
NULL 処理関数の一覧と使い分け早見表
| 関数 | 構文 | 動作 | SQL標準 |
|---|---|---|---|
| NVL | NVL(val, default) |
val が NULL なら default を返す | Oracle独自 |
| NVL2 | NVL2(val, not_null, null_val) |
val が NULL でなければ not_null、NULL なら null_val を返す | Oracle独自 |
| COALESCE | COALESCE(val1, val2, ...) |
左から順に評価し、最初の非 NULL を返す | SQL標準(推奨) |
| NULLIF | NULLIF(val1, val2) |
val1 = val2 なら NULL、それ以外は val1 を返す | SQL標準 |
| LNNVL | LNNVL(condition) |
条件が FALSE または UNKNOWN なら TRUE を返す | Oracle独自 |
NVL:NULL を別の値に置き換える基本関数
最もシンプルな NULL 置換関数です。NVL(値, デフォルト値) で、値が NULL の場合にデフォルト値を返します。両引数はデータ型が一致(または暗黙変換可能)である必要があります。
-- 基本: NULL を 0 に置換
SELECT employee_id,
NVL(commission_pct, 0) AS commission
FROM employees;
-- 文字列の NULL を別の文字列に置換
SELECT employee_id,
NVL(TO_CHAR(manager_id), '(トップ)') AS manager
FROM employees;
-- NVL を使った計算(NULL が混在しても正しく集計できる)
SELECT employee_id,
salary + NVL(commission_pct, 0) * salary AS total_income
FROM employees;
-- ※ commission_pct が NULL の場合、NVL なしだと total_income が NULL になる
-- WHERE 句での使用(NULL を含む列に条件をかける場合)
SELECT * FROM products
WHERE NVL(discontinued, 'N') = 'N'; -- discontinued が NULL のものも含める
NVL(expr, default) は expr と default の両方を必ず評価します。default 部分が重いサブクエリや関数呼び出しの場合、expr が NULL でなくても実行されます。この場合は CASE WHEN を使うか、後述の COALESCE(短絡評価)を検討してください。
NVL2:NULL か否かで異なる値を返す
NVL2(値, NULLでない場合, NULLの場合) は、CASE WHEN IS NULL の短縮形です。NVL(val, default) では「NULL 以外のときは元の値をそのまま使う」しかできませんが、NVL2 は NULL 以外の場合も別の値に変換できます。
-- 基本: commission_pct が NULL かどうかで表示を切り替える
SELECT employee_id,
NVL2(commission_pct,
'コミッションあり', -- NULL でない場合
'コミッションなし') -- NULL の場合
AS commission_status
FROM employees;
-- NVL2 と NVL の比較
-- NVL: NULL → default、NULL 以外 → 元の値(そのまま)
-- NVL2: NULL → null_val、NULL 以外 → not_null_val(変換も可能)
-- 数値変換例: NULL なら 0、NULL でなければ 1
SELECT employee_id,
NVL2(manager_id, 1, 0) AS has_manager
FROM employees;
-- CASE WHEN と同等(NVL2 の方が簡潔)
SELECT employee_id,
CASE WHEN commission_pct IS NOT NULL THEN 'あり'
ELSE 'なし'
END AS commission_status
FROM employees;
COALESCE:複数の候補から最初の非 NULL を返す(SQL 標準・推奨)
COALESCE(val1, val2, ..., valN) は、引数を左から順に評価し最初の非 NULL 値を返します。SQL 標準関数のため、PostgreSQL・MySQL・SQL Server など他の RDBMS にも移植しやすいため、NVL より COALESCE を推奨します。
-- 優先順位のある NULL 処理: preferred_name → nickname → last_name の順で最初の非NULLを使う
SELECT employee_id,
COALESCE(preferred_name, nickname, last_name) AS display_name
FROM employees;
-- NVL との比較(引数2個のときは同じ動作)
SELECT NVL(commission_pct, 0) AS nvl_result,
COALESCE(commission_pct, 0) AS coalesce_result
FROM employees;
-- 結果は同じだが COALESCE は SQL 標準なので移植性が高い
-- 複数テーブルの列をフォールバック的に参照
SELECT o.order_id,
COALESCE(o.delivery_address, c.default_address, '住所未登録') AS ship_to
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;
-- COALESCE は最初の非NULLが見つかった時点で残りの評価を打ち切る(短絡評価)
-- 重い処理のデフォルト値を後ろに置くと、前の値が非NULLなら実行されない
SELECT product_id,
COALESCE(
cached_price, -- まずキャッシュ価格を確認
(SELECT price FROM price_master -- なければマスターから取得(重い)
WHERE product_id = p.product_id),
0 -- それもなければ 0
) AS effective_price
FROM products p;
-- NVL は両方必ず評価するため、上記のようなパターンは COALESCE が有利
NULLIF:特定の値を NULL に変換する
NULLIF(val1, val2) は val1 = val2 のとき NULL を返し、それ以外は val1 を返します。「ゼロ除算回避」や「特定のダミー値を NULL に変換する」ときによく使います。
-- ゼロ除算を回避する(NULLIF で分母が 0 のとき NULL にする)
SELECT department_id,
total_sales / NULLIF(headcount, 0) AS avg_sales_per_person
FROM dept_summary;
-- headcount が 0 なら NULLIF(headcount, 0) = NULL → 割り算が NULL になる(ORA-01476 を回避)
-- ダミー値(-1 や 'N/A')を NULL に変換して正規化
SELECT product_id,
NULLIF(price, -1) AS price, -- -1 は未設定のダミー値 → NULL に変換
NULLIF(category, 'N/A') AS category -- 'N/A' → NULL に変換
FROM raw_import;
-- NULLIF と DECODE の比較
-- NULLIF(v, x) ≡ CASE WHEN v = x THEN NULL ELSE v END
-- DECODE(v, x, NULL, v) と同義だが NULLIF の方が意図が明確
-- 更新時に特定の値を NULL にリセット
UPDATE employees
SET manager_id = NULLIF(p_manager_id, 0) -- 0 が入力された場合は NULL(トップ扱い)
WHERE employee_id = p_emp_id;
LNNVL:NULL を含む条件を正しく扱う WHERE 句専用関数
LNNVL(condition) は条件が FALSE または UNKNOWN(NULL を含む比較)のとき TRUE を返します。「列の値が x ではない、または NULL である」行を取得したいときに、IS NULL との OR 記述を省略できます。
-- LNNVL なしの書き方(冗長) SELECT * FROM employees WHERE department_id != 10 OR department_id IS NULL; -- department_id が NULL の行も含めたい -- LNNVL を使った書き方(簡潔) SELECT * FROM employees WHERE LNNVL(department_id = 10); -- department_id = 10 が FALSE(10 以外)または UNKNOWN(NULL)の行を返す -- 実務パターン: バッチ処理で「処理済み(is_processed=1)以外」を取得 SELECT * FROM work_queue WHERE LNNVL(is_processed = 1); -- is_processed が 0 または NULL(未設定)の行をすべて取得
NOT IN に NULL が含まれると全件除外される落とし穴
Oracle の NULL に関するトラブルで最も影響が大きいのが NOT IN のサブクエリに NULL が含まれる問題です。この挙動は SQL 標準に基づくもので、NULL の3値論理から必然的に生じます。
-- サンプルデータ
CREATE TABLE depts (dept_id NUMBER);
INSERT INTO depts VALUES (10);
INSERT INTO depts VALUES (20);
INSERT INTO depts VALUES (NULL); -- NULL が1件含まれている
-- IN は問題なく動作する
SELECT * FROM employees
WHERE department_id IN (SELECT dept_id FROM depts);
-- → department_id が 10 または 20 の行が返る(NULLはスキップ)
-- NOT IN はサブクエリに NULL が1件でもあると全件が除外される
SELECT * FROM employees
WHERE department_id NOT IN (SELECT dept_id FROM depts);
-- → 0件(!)
-- 理由: NOT IN (10, 20, NULL) は
-- department_id != 10 AND department_id != 20 AND department_id != NULL
-- 最後の "department_id != NULL" が UNKNOWN になるため
-- 全体が UNKNOWN → WHERE 条件を満たさない → 全件除外
-- 対処法1: WHERE 句で NULL を除外する
SELECT * FROM employees
WHERE department_id NOT IN (
SELECT dept_id FROM depts WHERE dept_id IS NOT NULL
);
-- 対処法2: NOT EXISTS を使う(NULL に対して安全)
SELECT * FROM employees e
WHERE NOT EXISTS (
SELECT 1 FROM depts d
WHERE d.dept_id = e.department_id -- NULL = NULL は FALSE なのでマッチしない
);
-- NOT EXISTS は NULL を自動的に安全に処理するため推奨
サブクエリの結果に NULL が含まれる可能性がある場合、
NOT IN は意図せず全件除外されるバグの原因になります。NOT EXISTS は NULL に対して安全な動作をするため、サブクエリを使った否定条件には NOT EXISTS を使う習慣をつけることを推奨します。
NULL を含む集計関数の挙動
Oracle の集計関数(COUNT・SUM・AVG等)は NULL を無視します。この挙動が意図しない集計結果を生む場合があります。
-- テストデータ(salary に NULL が含まれる)
-- emp_id: 1, salary: 300000
-- emp_id: 2, salary: 400000
-- emp_id: 3, salary: NULL ← 給与未設定
SELECT COUNT(*) AS total_rows, -- 3(NULL も含めて行数カウント)
COUNT(salary) AS salary_count, -- 2(NULL を除いた件数)
SUM(salary) AS total_salary, -- 700000(NULL を無視)
AVG(salary) AS avg_salary, -- 350000(700000 / 2 ← NULL を除いた件数で割る)
AVG(NVL(salary,0)) AS avg_with_zero -- 233333(700000 / 3 ← 全員で割りたい場合)
FROM employees;
-- ポイント: AVG(salary) は NULL を除いた行の平均になる点に注意
-- 「全員の平均」が必要なら NVL(salary, 0) でゼロ補完してから AVG
-- GROUP BY での NULL: Oracle では NULL もひとつのグループとして扱われる
SELECT department_id, COUNT(*) AS headcount
FROM employees
GROUP BY department_id
ORDER BY department_id NULLS LAST; -- NULL グループを最後に
DECODE での NULL 処理
Oracle の DECODE 関数は NULL を = で比較しても正しく処理します。これは通常の = NULL が UNKNOWN になるルールの例外で、DECODE 内部では NULL 同士の比較が TRUE になります。
-- DECODE は NULL == NULL を TRUE として扱う(通常の = とは異なる)
SELECT employee_id,
DECODE(manager_id,
NULL, 'トップ管理職', -- manager_id が NULL の場合
manager_id) -- NULL でない場合はそのまま
AS manager_label
FROM employees;
-- CASE WHEN では IS NULL が必要(通常のルール)
SELECT employee_id,
CASE WHEN manager_id IS NULL THEN 'トップ管理職'
ELSE TO_CHAR(manager_id)
END AS manager_label
FROM employees;
-- どちらも同じ結果を返す。CASE WHEN の方が意図が明確でSQL標準準拠のため推奨
ORDER BY と NULL:NULLS FIRST / NULLS LAST
Oracle は ORDER BY で NULL の位置を制御する NULLS FIRST / NULLS LAST 句をサポートします(Oracle 9i 以降)。
-- デフォルト: Oracle では昇順(ASC)で NULL は最後、降順(DESC)で NULL は最初
SELECT employee_id, salary
FROM employees
ORDER BY salary ASC; -- NULL が最後(Oracleのデフォルト)
-- 明示的に制御
SELECT employee_id, salary
FROM employees
ORDER BY salary ASC NULLS FIRST; -- NULL を先頭に
ORDER BY salary ASC NULLS LAST; -- NULL を末尾に(昇順デフォルトと同じ)
ORDER BY salary DESC NULLS FIRST; -- 降順でも NULL を先頭に(降順デフォルトと同じ)
ORDER BY salary DESC NULLS LAST; -- 降順で NULL を末尾に
-- 分析関数でも NULLS FIRST / NULLS LAST が使える
SELECT employee_id, salary,
RANK() OVER (ORDER BY salary DESC NULLS LAST) AS salary_rank
FROM employees;
NULL 処理関数の使い分けまとめ
| やりたいこと | 推奨関数 | 例 |
|---|---|---|
| NULL を別の値に置換(1対1) | COALESCE / NVL | COALESCE(salary, 0) |
| 複数の候補から最初の非NULLを選ぶ | COALESCE | COALESCE(a, b, c, 0) |
| NULL か否かで別の値を返す | NVL2 / CASE WHEN | NVL2(col, 'あり', 'なし') |
| 特定の値を NULL に変換 | NULLIF | NULLIF(price, -1) |
| ゼロ除算を NULL に変換して回避 | NULLIF | a / NULLIF(b, 0) |
| NULL を含む否定条件 | NOT EXISTS / LNNVL | WHERE LNNVL(col = 10) |
| サブクエリでの否定(NULL安全) | NOT EXISTS | WHERE NOT EXISTS (...) |
まとめ
Oracle における NULL は「3値論理」に基づいており、その挙動を正しく理解することが堅牢な SQL 設計の基本です。
特に注意すべき点は以下の2つです。
- NOT IN のサブクエリに NULL が含まれると全件除外される:常に NOT EXISTS で代替することを推奨
- AVG は NULL を除外した件数で割る:全員を母数にしたい場合は NVL でゼロ補完
NULL 置換関数は SQL 標準の COALESCE を基本とし、Oracle 固有の NVL2 や NULLIF を用途に応じて組み合わせてください。NVL 関数の基本的な使い方はNVL関数でNULLを指定した値に置き換える方法も合わせて参照してください。

