COALESCE(コアレス)は、引数を左から順に見て、最初に見つかった NULL でない値を返す関数です。COALESCE(points, 0) と書けば「points が NULL なら 0、そうでなければ points の値」になります。 標準 SQL の関数なので、MySQL・PostgreSQL・SQLite・SQL Server・Oracle のどれでも同じ書き方で動きます。IFNULL(MySQL・SQLite)・NVL(Oracle)・ISNULL(SQL Server)は、同じ目的の DB 固有の関数です。

SELECT COALESCE(points, 0)                      AS points,        -- NULL なら 0
       COALESCE(nickname, name)                 AS display_name,  -- NULL なら別の列
       COALESCE(nickname, email, name, 'guest') AS label          -- 3つ以上も並べられる
FROM members;

やりたいこと別の早見表(5つのDBで共通)

自分の状況の行を見ると、1行の書き方と注意点が分かります。例はこの記事のテーブル members(会員)と orders(注文)を使っています。

やりたいこと 書き方 注意点
NULL を 0 にして表示する COALESCE(points, 0) 保存データを 0 で上書きはしない(後述)
NULL なら別の列、それも NULL なら固定値 COALESCE(nickname, email, name, 'guest') 引数はいくつでも。左から順に見る
数値の列を文字で表示し、NULL は「none」 COALESCE(CAST(points AS CHAR), 'none') 型をそろえる。PostgreSQL はそろえないとエラー
空文字も NULL と同じ扱いにする COALESCE(NULLIF(nickname, ''), '未設定') COALESCE は空文字を置き換えない
0 で割るエラーを避ける points / NULLIF(visits, 0) 0 のときの結果は NULL
0 件のとき合計を NULL でなく 0 にする COALESCE(SUM(amount), 0) SUM(COALESCE(amount, 0)) では 0 件のとき NULL
NULL を 0 として全員で平均する AVG(COALESCE(points, 0)) AVG(points) は NULL の人を分母に入れない
LEFT JOIN で相手がいない行を 0 にする COALESCE(SUM(o.amount), 0) と COUNT(o.id) COUNT(*) は相手がいなくても 1 になる
NULL を含めて「〜でない」行を取る COALESCE(nickname, '') <> 'ao' nickname <> 'ao' は NULL の行を返さない
並べ替えで NULL を末尾にする ORDER BY COALESCE(points, -1) DESC PostgreSQL・SQLite は NULLS LAST も使える
GROUP BY で NULL をひとつの名前にまとめる GROUP BY COALESCE(nickname, '(なし)') 空文字は別のグループになる
NULL のときだけ既定の行と結合する JOIN fees f ON f.pref = COALESCE(s.pref, 'その他') 結合列のインデックスは効きにくい
NULL でなければ A、NULL なら B(NVL2 相当) CASE WHEN email IS NOT NULL THEN 'A' ELSE 'B' END NVL2 は Oracle だけ
文字列連結で NULL になるのを防ぐ CONCAT_WS('-', a, b) か COALESCE(列, '') 'a' || NULL と MySQL の CONCAT は NULL
更新で NULL を 0 とみなして加算する SET points = COALESCE(points, 0) + 10 points + 10 は NULL のまま

DB別対応表:COALESCE・IFNULL・NVL・NVL2・ISNULL・NULLIF

○は使える、×は使えない(エラーになった、またはその DB にない)です。MySQL・PostgreSQL・SQLite の列は実行結果、SQL Server・Oracle の列は公式ドキュメントにもとづきます。

関数 働き MySQL 8.0 PostgreSQL 16 SQLite 3.45
COALESCE(a, b, ...) 最初の NULL でない値 ○ ○ ○(引数は2個以上)
IFNULL(a, b) a が NULL なら b ○ × ○
NVL(a, b) a が NULL なら b × × ×
NVL2(a, b, c) a が NULL でなければ b、NULL なら c × × ×
ISNULL(a, b) a が NULL なら b ×(1引数の判定関数) ×(後置の演算子) ×(後置の演算子)
NULLIF(a, b) a = b なら NULL、違えば a ○ ○ ○
関数 SQL Server Oracle どの DB でも動く書き方
COALESCE(a, b, ...) ○ ○(引数は2個以上) そのまま
IFNULL(a, b) × × COALESCE(a, b)
NVL(a, b) × ○ COALESCE(a, b)
NVL2(a, b, c) × ○ CASE WHEN a IS NOT NULL THEN b ELSE c END
ISNULL(a, b) ○ × COALESCE(a, b)(型の決まり方が違う。後述)
NULLIF(a, b) ○ ○ そのまま

迷ったら COALESCE と NULLIF の2つを使うと、5つの DB で同じ SQL になります。

この記事と関連記事の分担

知りたいこと 読む記事
NULL そのものの性質(比較・集計・IS NULL)と落とし穴 SQLのNULLの扱い|COALESCE・NULLIFと落とし穴12選
CASE 式の書き方(値や範囲で分岐する) SQL CASE式の使い方|用途別コピペ集とDB比較・実行結果つき
集計関数ごとの NULL の扱い SQL集計関数一覧|SUM・AVG・MAX・MIN・COUNTとNULLの扱い
INNER JOIN と LEFT JOIN で NULL が生まれる仕組み SQL INNER JOINとLEFT JOINの違い|JOIN種類早見表と落とし穴
列を関数で包む以外にインデックスが効かない書き方 SQLのインデックスが効かない10のパターンと確認方法

確認環境:PostgreSQL 16.15(Ubuntu 24.04、psql。NULL を見分けやすいよう \pset null 'NULL' で表示)、MySQL 8.0.46(mysql クライアント、--table)、SQLite 3.45.1(sqlite3、.nullvalue NULL)で、この記事の SQL を実行し、出力をそのまま載せています。EXPLAIN は 100 万行のテーブルで実行しました。SQL Server は Microsoft Learn の「COALESCE」「ISNULL」「NULLIF」と「Database Engine events and errors」、Oracle は Oracle Database 23ai SQL Language Reference の「COALESCE」「NVL」「NVL2」「Nulls」で確かめました(確認日 2026年10月16日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「SQL COALESCEの使い方|IFNULL・NVL・ISNULL対応表と実行結果」

この記事で使うテーブルとデータ(コピペ用)

会員 members(NULL と空文字の両方を含む)と、注文 orders(会員 1 と 3 だけに注文がある)の2テーブルです。PostgreSQL・MySQL・SQLite のどれでも、このまま貼って実行できます。

DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS members;
CREATE TABLE members (
  id       INT PRIMARY KEY,
  name     VARCHAR(20) NOT NULL,
  nickname VARCHAR(20),
  email    VARCHAR(50),
  points   INT,
  visits   INT
);
INSERT INTO members (id, name, nickname, email, points, visits) VALUES
  (1, 'Aoki',  'ao', 'aoki@example.com', 120,  4),
  (2, 'Baba',  NULL, 'baba@example.com', NULL, 0),
  (3, 'Chiba', '',   NULL,               80,   2),
  (4, 'Doi',   NULL, NULL,               NULL, NULL);
CREATE TABLE orders (
  id        INT PRIMARY KEY,
  member_id INT NOT NULL,
  amount    INT NOT NULL
);
INSERT INTO orders (id, member_id, amount) VALUES
  (101, 1, 3000), (102, 1, 1500), (103, 3, 2000);
 id | name  | nickname |      email       | points | visits
----+-------+----------+------------------+--------+--------
  1 | Aoki  | ao       | aoki@example.com |    120 |      4
  2 | Baba  | NULL     | baba@example.com |   NULL |      0
  3 | Chiba |          | NULL             |     80 |      2
  4 | Doi   | NULL     | NULL             |   NULL |   NULL
(4 rows)

id = 3 の nickname は NULL ではなく空文字(長さ 0 の文字列)です。この違いが、あとで出てくる「COALESCE したのに空欄のまま」の原因になります。

COALESCEの基本の使い方(左から最初のNULLでない値を返す)

NULLを0や別の列に置き換える

SELECT id,
       COALESCE(nickname, name) AS display_name,
       COALESCE(points, 0)      AS points
FROM members
ORDER BY id;

MySQL 8.0 の実行結果です。PostgreSQL・SQLite でも同じ値が返りました。

+----+--------------+--------+
| id | display_name | points |
+----+--------------+--------+
|  1 | ao           |    120 |
|  2 | Baba         |      0 |
|  3 |              |     80 |
|  4 | Doi          |      0 |
+----+--------------+--------+
実行前 members(使う列)
idnamenicknamepoints
1Aokiao120
2BabaNULLNULL
3Chiba''(空文字)80
4DoiNULLNULL
▼ 4 rows
実行結果(置き換わった所に色)
iddisplay_namepoints
1ao120
2Baba NULL0 NULL
380
4Doi NULL0 NULL

NULL だった id = 2・4 だけが name と 0 に置き換わり、空文字の id = 3 の display_name は空欄のまま

id = 2 と 4 は nickname が NULL なので name が、points が NULL なので 0 が入りました。id = 3 の display_name が空欄なのは、nickname が空文字だからです。 空文字は NULL ではないので、COALESCE は空文字をそのまま返します。空文字も置き換える書き方は「NULLIFの使い方」の節で扱います。

引数を3つ以上並べる

「ニックネーム → メール → 名前 → 固定値」の順で、最初に見つかった値を採用する例です。最後に固定値を置いておけば、結果が NULL になることはありません。

SELECT id, COALESCE(nickname, email, name, 'guest') AS label
FROM members
ORDER BY id;
 id |      label
----+------------------
  1 | ao
  2 | baba@example.com
  3 |
  4 | Doi
(4 rows)
引数の中身(左から見て選ばれたセルに色)
idnicknameemailname
1aoaoki@example.comAoki
2NULLbaba@example.comBaba
3''(空文字)NULLChiba
4NULLNULLDoi
▼ 4 rows
COALESCE(nickname, email, name, 'guest') の結果
idlabel
1ao
2baba@example.com
3
4Doi

id = 3 は空文字が NULL でないので nickname で止まり、'guest' まで進んだ行はない

COALESCE は、左から順に「NULL か」を調べ、NULL でない値が見つかった時点で結果を返します。右側の引数はその時点で見なくなります(実測は「引数の評価」の節)。

flowchart TD
  A["COALESCE(a, b, c)"] --> B{"a は NULL?"}
  B -- "いいえ" --> R1["a を返す(b と c は評価しない)"]
  B -- "はい" --> C{"b は NULL?"}
  C -- "いいえ" --> R2["b を返す(c は評価しない)"]
  C -- "はい" --> D{"c は NULL?"}
  D -- "いいえ" --> R3["c を返す"]
  D -- "はい" --> R4["NULL を返す"]

すべてNULLなら結果もNULL、引数の数にはDBで差がある

COALESCE(NULL, NULL) と、全部 NULL の列を並べた COALESCE(points, visits)(id = 4)は、3つの DB すべてで NULL を返しました。引数の数については次の違いがありました。

書き方 MySQL 8.0.46 PostgreSQL 16.15 SQLite 3.45.1
COALESCE(1)(引数1個) 1 1 Parse error: wrong number of arguments to function COALESCE()
COALESCE()(引数なし) ERROR 1064 (42000): You have an error in your SQL syntax; ... near ')' ERROR: syntax error at or near ")" Parse error: wrong number of arguments to function COALESCE()
COALESCE(NULL, NULL) NULL NULL NULL

Oracle のリファレンスにも「2つ以上の式を指定する」とあります。SQL Server は、ドキュメントで「すべての引数が NULL なら NULL を返す。ただし少なくとも1つは型付きの NULL であること」と説明されており、COALESCE(NULL, NULL) のように NULL 定数だけを並べると、エラー 4127 At least one of the arguments to COALESCE must be an expression that is not the NULL constant. になります。引数は2個以上、少なくとも1つは列か型の決まった値、と覚えておくとどの DB でも通ります。

MySQLのCOALESCE|IFNULL・ISNULL・NVL・IFとの違い

MySQL には COALESCE のほかに IFNULL があり、ISNULL は SQL Server と意味が違い、NVL はありません。同じ SQL を MySQL 8.0.46 に流した結果です。

書き方 MySQL 8.0.46 の結果 意味
COALESCE(points, 0) 120, 0, 80, 0 標準 SQL。引数はいくつでも
IFNULL(points, 0) 120, 0, 80, 0 2引数だけの置き換え関数
IFNULL(NULL, NULL, 'x') ERROR 1582 (42000): Incorrect parameter count in the call to native function 'IFNULL' 3つ以上は COALESCE で
ISNULL(points) 0, 1, 0, 1 判定の関数。NULL なら 1、そうでなければ 0
ISNULL(NULL, 'x') ERROR 1582 (42000): Incorrect parameter count in the call to native function 'ISNULL' SQL Server の書き方は通らない
NVL(NULL, 'x') ERROR 1305 (42000): FUNCTION r28_demo.NVL does not exist NVL は Oracle の関数(r28_demo は DB 名)
NVL2(NULL, 'a', 'b') ERROR 1305 (42000): FUNCTION r28_demo.NVL2 does not exist CASE WHEN ... IS NOT NULL THEN a ELSE b END で
IF(email IS NULL, 'none', 'registered') registered, registered, none, none MySQL の3引数関数。NVL2 相当に使える
IIF(NULL IS NULL, 'a', 'b') ERROR 1305 (42000): FUNCTION r28_demo.IIF does not exist IIF は SQLite・SQL Server
-- MySQL:置き換え(IFNULL)と判定(ISNULL)を並べて実行
SELECT id, IFNULL(points, 0) AS p, ISNULL(points) AS is_null FROM members ORDER BY id;
+----+-----+---------+
| id | p   | is_null |
+----+-----+---------+
|  1 | 120 |       0 |
|  2 |   0 |       1 |
|  3 |  80 |       0 |
|  4 |   0 |       1 |
+----+-----+---------+

Oracle から MySQL に移すときは、NVL(a, b) を COALESCE(a, b) に、NVL2(a, b, c) を IF(a IS NOT NULL, b, c) か CASE 式に書き換えます。 逆に MySQL で書いた IFNULL を PostgreSQL に持っていくと、ERROR: function ifnull(unknown, unknown) does not exist で止まります。最初から COALESCE で書いておくと、この書き換えが要りません。

MySQLの戻り値の型は「引数全体をまとめた型」になる

MySQL のドキュメントでは、COALESCE の戻り値の型は「引数の型をまとめた(aggregated)型」、IFNULL は「2つの式のうち、より一般的なほう(STRING → REAL → INTEGER の順)」と説明されています。CREATE TABLE ... AS SELECT で結果を表にすると、実際の型が見えます。

CREATE TABLE t_type AS
SELECT COALESCE(points, 0)      AS c_int,
       COALESCE(points, 'none') AS c_mix,
       COALESCE(1, 2.5)         AS c_dec,
       COALESCE(1, 'a')         AS c_lit,
       IFNULL(1, 'test')        AS c_ifnull,
       COALESCE(NULL, NULL)     AS c_null
FROM members WHERE id = 1;
SHOW COLUMNS FROM t_type;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| c_int    | int          | NO   |     | 0       |       |
| c_mix    | varchar(11)  | NO   |     |         |       |
| c_dec    | decimal(2,1) | NO   |     | 0.0     |       |
| c_lit    | varchar(2)   | NO   |     |         |       |
| c_ifnull | varchar(4)   | NO   |     |         |       |
| c_null   | binary(0)    | YES  |     | NULL    |       |
+----------+--------------+------+-----+---------+-------+
  • COALESCE(points, 'none') は varchar になりました。値は 120 ですが、数値ではなく文字列の '120' です。
  • COALESCE(1, 2.5) は 1.0(decimal)、COALESCE(1, 'a') は文字列の '1' です。返る値は1つ目でも、型は引数全体を見て広いほうに決まります。
  • IFNULL(1, 'test') も varchar(4) で、ドキュメントの説明どおり文字列型になりました。

型が混ざるとMySQLは暗黙変換で動き続ける(エラーにならない)

PostgreSQL は型が合わないとエラーで止まります(次の節)が、MySQL は文字列として処理を続けます。そのため、気づかないうちに結果が変わります。

-- 並べ替えが文字列順になる('120' < '80' < 'none')
SELECT id, points FROM members ORDER BY COALESCE(points, 'none');
+----+--------+
| id | points |
+----+--------+
|  1 |    120 |
|  3 |     80 |
|  2 |   NULL |
|  4 |   NULL |
+----+--------+

-- 'none' が数値 0 に変換され、NULL の行が = 0 に当てはまる
SELECT id, points FROM members WHERE COALESCE(points, 'none') = 0;
+----+--------+
| id | points |
+----+--------+
|  2 |   NULL |
|  4 |   NULL |
+----+--------+

120 が 80 より前に並んでいます。数値として並べたいなら COALESCE(points, 0) や COALESCE(points, -1) のように、同じ型の代替値を使います。代替値の型は列の型に合わせるのが、5つの DB に共通する原則です。

DB別:IFNULL・NVL・NVL2・ISNULL・IIFとCOALESCEの違い

PostgreSQL:COALESCEとNULLIFだけ。ISNULLは後置の演算子

PostgreSQL 16.15 で IFNULL・NVL・ISNULL を関数として呼ぶと、すべて「関数が存在しない」になります。

書き方 PostgreSQL 16.15 の結果
IFNULL(NULL, 'x') ERROR: function ifnull(unknown, unknown) does not exist
NVL(NULL, 'x') ERROR: function nvl(unknown, unknown) does not exist
NVL2(NULL, 'a', 'b') ERROR: function nvl2(unknown, unknown, unknown) does not exist
ISNULL(NULL, 'x') ERROR: function isnull(unknown, unknown) does not exist
IF(1 = 1, 'a', 'b') ERROR: function if(boolean, unknown, unknown) does not exist
SELECT NULL ISNULL t(IS NULL の別の書き方。後ろに置く)

PostgreSQL のドキュメントは「COALESCE は標準 SQL の関数で、他の DB の NVL や IFNULL に相当する」と説明しています。

SQLite:IFNULLとIIFがあり、ISNULLは演算子

SQLite 3.45.1 では IFNULL と IIF が使え、ISNULL は 値 ISNULL と後ろに書く演算子です。関数の形で書くと構文エラーになります。

書き方 SQLite 3.45.1 の結果
IFNULL(NULL, 'x') x
IFNULL(NULL, NULL, 'x') Parse error: wrong number of arguments to function IFNULL()
ISNULL(NULL, 'x')/ISNULL(NULL) Parse error: near "ISNULL": syntax error
NULL ISNULL/1 ISNULL/NULL NOTNULL 1/0/0(判定の演算子)
IIF(email IS NULL, 'none', 'registered') registered, registered, none, none
NVL(NULL, 'x') Parse error: no such function: NVL
-- SQLite:判定(ISNULL 演算子)と置き換え(IFNULL)
SELECT id, points ISNULL AS is_null, IFNULL(points, 0) AS p FROM members ORDER BY id;
id  is_null  p
--  -------  ---
1   0        120
2   1        0
3   0        80
4   1        0

SQLite のドキュメントでは、ifnull() は「引数2個の coalesce() と同じ」、iif(X, Y, Z) は「CASE WHEN X THEN Y ELSE Z END と同じバイトコードになる」と説明されています。2引数の iif() と if() という別名は 3.48.0(2025年1月)、4つ以上の引数は 3.49.0 で加わりました。3.45.1 では IF(NULL IS NULL, 'a') が no such function: IF になりました。

SQL Server:ISNULLは置き換え、COALESCEとは型と評価回数が違う

SQL Server の ISNULL(a, b) は「a が NULL なら b」を返す置き換えの関数です(MySQL の ISNULL とは別物)。Microsoft Learn「COALESCE」の「Compare COALESCE and ISNULL」に、両者の違いが列挙されています。

項目 ISNULL(SQL Server) COALESCE(SQL Server)
引数の数 2つ いくつでも
戻り値の型 第1引数の型 CASE 式の規則で、優先順位がいちばん高い型
代替値が長いとき 第1引数の型に変換され、長いと切り詰められることがある 切り詰めの記述なし
評価の回数 関数なので1回 CASE 式に展開され、入力が複数回評価されることがある
結果の NULL 許容 NULL 非許容として扱われる NULL でない引数を渡しても NULL 許容と見なされる

たとえば ISNULL(CAST(NULL AS VARCHAR(3)), 'abcdef') は、ドキュメントの規則どおりなら第1引数の VARCHAR(3) に合わせて切り詰められます。同じ形を MySQL 8.0.46 の IFNULL と COALESCE で試すと、どちらも varchar(6) になり abcdef がそのまま返りました。同じ「2引数で NULL を置き換える関数」でも、SQL Server の ISNULL と MySQL の IFNULL では型の決まり方が違います。 SQL Server から移すときは、CAST で型を明示しておくと動きがそろいます。

Oracle:NVL・NVL2と、空文字がNULLになる点

Oracle Database 23ai のリファレンスにもとづく整理です。

関数 働き 型の決まり方(リファレンス)
NVL(expr1, expr2) expr1 が NULL なら expr2 expr1 が文字なら expr2 を expr1 の型に変換して VARCHAR2 を返す。数値なら優先順位の高い数値型
NVL2(expr1, expr2, expr3) expr1 が NULL でなければ expr2、NULL なら expr3 expr2 が文字なら expr3 を expr2 の型に。expr2・expr3 に LONG は使えない
COALESCE(expr1, expr2, ...) 最初の NULL でない値 数値なら優先順位の高い数値型。短絡評価を行うと明記

Oracle は「長さ 0 の文字値を NULL として扱う」と明記されています(将来の版でも同じとは限らないので、空文字と NULL を同じものとして扱わないことを推奨する注記つき)。つまり Oracle では NVL(nickname, '未設定') だけで空文字も置き換わります。他の DB に移すと空文字はそのまま残るので、COALESCE(NULLIF(nickname, ''), '未設定') の形にしておくと、Oracle でも他の DB でも同じ結果になります。

IFNULL・NVL・ISNULL・COALESCEの違い(引数の数・型・移植性)

関数 使える DB 引数の数 戻り値の型の決まり方 補足
COALESCE 5つすべて 2個以上いくつでも 引数全体から決まる(MySQL はまとめた型、PostgreSQL は共通の型に変換できないとエラー、SQL Server は優先順位の高い型) 標準 SQL。必要な引数だけ評価(SQL Server はサブクエリが2回評価されることがある)
IFNULL MySQL・SQLite 2個 MySQL は STRING → REAL → INTEGER の順で一般的なほう 3個渡すと ERROR 1582
NVL Oracle 2個 1つ目が文字なら2つ目をその型に変換して VARCHAR2、数値なら優先順位の高い数値型 Oracle は空文字を NULL として扱う
NVL2 Oracle 3個 2つ目と3つ目で決まる(2つ目が文字なら3つ目をその型に) 他の DB は CASE 式で
ISNULL(置き換え) SQL Server 2個 第1引数の型。長い代替値は切り詰められることがある 関数なので評価は1回
ISNULL(判定) MySQL 1個 0 か 1 PostgreSQL・SQLite は a ISNULL の後置演算子

DB別の書き換え表(移植するときの対応)

元の書き方 元の DB どの DB でも動く書き方
IFNULL(a, b) MySQL・SQLite COALESCE(a, b)
NVL(a, b) Oracle COALESCE(a, b)
ISNULL(a, b) SQL Server COALESCE(a, b)(型は第1引数に合わせて CAST)
NVL2(a, b, c) Oracle CASE WHEN a IS NOT NULL THEN b ELSE c END
IF(a IS NULL, b, c) MySQL CASE WHEN a IS NULL THEN b ELSE c END
IIF(a IS NULL, b, c) SQLite・SQL Server CASE WHEN a IS NULL THEN b ELSE c END
ISNULL(a)(判定) MySQL a IS NULL
a ISNULL(判定) PostgreSQL・SQLite a IS NULL

戻り値の型とエラー(PostgreSQLは型をそろえないとエラー)

置き換える値の型が元の列と違うとき、DB によって結果が変わります。COALESCE でいちばんつまずきやすい箇所です。

数値の列に文字列を渡す:PostgreSQLはエラー、MySQL・SQLiteは文字列

SELECT COALESCE(points, 'none') FROM members WHERE id = 2;
DB 結果
PostgreSQL 16.15 ERROR: invalid input syntax for type integer: "none"
MySQL 8.0.46 none(列全体が varchar として返る)
SQLite 3.45.1 none(行ごとに型が変わる)

PostgreSQL は、引数の型を1つにそろえてから評価します。'none' のような引用符だけの文字列は「型未定(unknown)」なので、まず integer として読もうとして失敗しました。型を明示するとエラー文が変わります。

SELECT COALESCE(points, 'none'::text) FROM members WHERE id = 2;
ERROR:  COALESCE types integer and text cannot be matched

SELECT COALESCE(points, nickname) FROM members WHERE id = 2;
ERROR:  COALESCE types integer and character varying cannot be matched

COALESCE types A and B cannot be matched は「A と B を同じ型にできない」という意味です。 直し方は、列のほうを文字列に変換してから渡すことです。

-- PostgreSQL・SQLite(MySQL は CAST(points AS CHAR))
SELECT id, COALESCE(CAST(points AS TEXT), 'none') AS points_text FROM members ORDER BY id;
 id | points_text
----+-------------
  1 | 120
  2 | none
  3 | 80
  4 | none
(4 rows)

CAST(points AS CHAR) は MySQL・PostgreSQL・SQLite の3つで動きます。CAST(points AS TEXT) は MySQL では構文エラー(1064)になるので、3つの DB で共通にしたい場合は AS CHAR のほうがそろいます。日付の列でも同じで、COALESCE(CURRENT_DATE, 'none') は PostgreSQL で ERROR: invalid input syntax for type date: "none" になりました。型変換の書き方は関連記事「SQL CASTで型変換|文字列・数値・日付の変換をDB別早見表で解説」で扱っています。

結果の型を確かめる方法

DB 確かめ方 結果の例
PostgreSQL 16 SELECT pg_typeof(COALESCE(points, 0)); COALESCE(points, 0) → integer、COALESCE(1, 2.5) → numeric、COALESCE(NULL, 'x') → text、COALESCE(NULL, NULL) → text
MySQL 8.0 CREATE TABLE t AS SELECT ...; → SHOW COLUMNS FROM t; 前節の表のとおり(int/varchar/decimal)
SQLite 3.45 SELECT typeof(COALESCE(points, 'none')); 行ごとに integer と text が混ざる
-- SQLite:同じ列の中で行ごとに型が変わる
SELECT id, COALESCE(points, 'none') AS p, typeof(COALESCE(points, 'none')) AS t FROM members ORDER BY id;
id  p     t
--  ----  -------
1   120   integer
2   none  text
3   80    integer
4   none  text

PostgreSQL のドキュメントでは、COALESCE の型は UNION や CASE と同じ規則で決まります。「すべて同じ型ならその型」「引用符だけの値(unknown)は無視して残りで決める」「型のカテゴリ(数値・文字列など)が違えば失敗」「暗黙変換できる側に寄せる」という順です。COALESCE(1, 2.5) が numeric になるのは、integer が numeric に暗黙変換できるからです。

集計とCOALESCE(COUNT・SUM・AVGはNULLを飛ばす)

集計関数は NULL の行を飛ばして計算します。 COALESCE をどこに置くかで結果が変わるので、何を求めたいかで書き分けます。

SELECT COUNT(*)                 AS c_all,
       COUNT(points)            AS c_pts,
       SUM(points)              AS s,
       AVG(points)              AS a,
       AVG(COALESCE(points, 0)) AS a0
FROM members;
+-------+-------+------+----------+---------+
| c_all | c_pts | s    | a        | a0      |
+-------+-------+------+----------+---------+
|     4 |     2 |  200 | 100.0000 | 50.0000 |
+-------+-------+------+----------+---------+
実行前 members の points(NULL の行に色)
idnamepoints
1Aoki120
2BabaNULL
3Chiba80
4DoiNULL
▼ 1 row
集計の結果
c_allc_ptsaa0
42100.000050.0000

COUNT(points) と AVG(points) は NULL の2行を飛ばし(200 ÷ 2)、AVG(COALESCE(points, 0)) は4行で割る(200 ÷ 4)

式 結果 意味
COUNT(*) 4 行の数(NULL も数える)
COUNT(points) 2 points が NULL でない行の数
SUM(points) 200 NULL を飛ばした合計
AVG(points) 100 値がある2人の平均(200 ÷ 2)
AVG(COALESCE(points, 0)) 50 NULL を 0 として4人で平均(200 ÷ 4)

「ポイントを持っている人の平均」なら AVG(points)、「全会員1人あたりの平均」なら AVG(COALESCE(points, 0)) です。どちらが正しいというものではなく、分母に誰を含めるかの違いです。逆に 0 を平均の対象から外したいときは AVG(NULLIF(visits, 0)) と書きます(3つの DB で、AVG(NULLIF(visits, 0)) は 3、AVG(visits) は 2 でした)。

0件のときSUMはNULL。COALESCE(SUM(x), 0) と SUM(COALESCE(x, 0)) の違い

条件に合う行が1つもないとき、COUNT は 0 を返しますが、SUM は NULL を返します。

SELECT SUM(points) AS s, COALESCE(SUM(points), 0) AS s0, COUNT(*) AS c
FROM members
WHERE id > 100;   -- 該当する行なし
+------+----+---+
| s    | s0 | c |
+------+----+---+
| NULL |  0 | 0 |
+------+----+---+
書き方 0件のとき NULL の行が混ざるとき
SUM(points) NULL NULL を飛ばして合計
SUM(COALESCE(points, 0)) NULL(0件なら SUM 自体が NULL) 合計は同じ
COALESCE(SUM(points), 0) 0 NULL を飛ばして合計

PostgreSQL 16 で SUM(COALESCE(points, 0)) を 0 件の条件で実行しても NULL でした。合計を画面に出したり計算に使ったりするなら、外側を COALESCE(SUM(列), 0) で包みます。 HAVING でも同じ形が使えます(HAVING COALESCE(SUM(o.amount), 0) < 3000)。集計関数ごとの NULL の扱いは関連記事「SQL集計関数一覧|SUM・AVG・MAX・MIN・COUNTとNULLの扱い」で、GROUP BY・HAVING の書き方は関連記事「SQL GROUP BY HAVING 使い方|集計関数一覧とDB比較・実行結果つき」で扱っています。

LEFT JOIN・JOIN条件・GROUP BY・ORDER BYでのCOALESCE

LEFT JOINで相手がいない行を0にする

LEFT JOIN で相手の行がないと、相手側の列は NULL になります。合計は COALESCE(SUM(...), 0)、件数は COUNT(相手の列) で数えます。

SELECT m.id, m.name,
       COALESCE(SUM(o.amount), 0) AS total,
       COUNT(o.id)                AS cnt,
       COUNT(*)                   AS cnt_all
FROM members m
LEFT JOIN orders o ON o.member_id = m.id
GROUP BY m.id, m.name
ORDER BY m.id;
 id | name  | total | cnt | cnt_all
----+-------+-------+-----+---------
  1 | Aoki  |  4500 |   2 |       2
  2 | Baba  |     0 |   0 |       1
  3 | Chiba |  2000 |   1 |       1
  4 | Doi   |     0 |   0 |       1
(4 rows)
members(左)
idname
1Aoki
2Baba
3Chiba
4Doi
orders(右)
idmember_idamount
10113000
10211500
10332000
▼ 4 rows
LEFT JOIN + GROUP BY の結果(注文のない会員に色)
nametotalcntcnt_all
Aoki450022
Baba0 NULL01
Chiba200011
Doi0 NULL01

COALESCE がないと Baba・Doi の total は NULL(打ち消し線の値)。cnt_all は相手がいなくても 1

COALESCE を外すと id = 2・4 の total は NULL になります。COUNT(*) は相手がいなくても 1 になる(LEFT JOIN で作られた NULL の行を数える)ので、注文件数は COUNT(o.id) のように相手側の列で数えます。MySQL 8.0・SQLite でも同じ結果でした。相関サブクエリで書く場合も同じで、COALESCE((SELECT SUM(o.amount) FROM orders o WHERE o.member_id = m.id), 0) とすると、注文のない会員は 0 になります(サブクエリが行を返さないと NULL になるため)。LEFT JOIN で NULL が生まれる仕組みは関連記事「SQL INNER JOINとLEFT JOINの違い|JOIN種類早見表と落とし穴」で扱っています。

JOIN条件でNULLを既定の行に結び付ける

送付先の都道府県 pref が NULL なら「その他」の送料を使う例です。結合条件に COALESCE を書くと、NULL の行が既定の行に結び付きます。

CREATE TABLE fees (pref VARCHAR(10) PRIMARY KEY, fee INT NOT NULL);
INSERT INTO fees VALUES ('東京', 500), ('大阪', 700), ('その他', 1000);
CREATE TABLE shipments (id INT PRIMARY KEY, pref VARCHAR(10));
INSERT INTO shipments VALUES (1, '東京'), (2, NULL), (3, '北海道');

SELECT s.id, s.pref, f.fee
FROM shipments s
JOIN fees f ON f.pref = COALESCE(s.pref, 'その他')
ORDER BY s.id;
 id | pref | fee
----+------+------
  1 | 東京 |  500
  2 | NULL | 1000
(2 rows)

id = 3(北海道)は fees に行がないので結合されません。「NULL も、表に無い値も、その他にする」なら、LEFT JOIN したあとに既定の行の値で埋めます。

SELECT s.id, s.pref, COALESCE(f.fee, d.fee) AS fee
FROM shipments s
LEFT JOIN fees f ON f.pref = s.pref
JOIN fees d ON d.pref = 'その他'
ORDER BY s.id;
 id |  pref  | fee
----+--------+------
  1 | 東京   |  500
  2 | NULL   | 1000
  3 | 北海道 | 1000
(3 rows)

結合キーが両方とも NULL の行は、ON a.k = b.k では結び付きません(NULL = NULL は真にならないため)。ON COALESCE(a.k, '') = COALESCE(b.k, '') と書くと結び付きますが、インデックスが効きにくくなります。PostgreSQL の a.k IS NOT DISTINCT FROM b.k、MySQL の a.k <=> b.k も同じ結果で、2行('x' と NULL 同士)が結合されました。

GROUP BYでNULLをひとつの名前にまとめる

SELECT COALESCE(nickname, '(なし)') AS nick, COUNT(*) AS cnt
FROM members
GROUP BY COALESCE(nickname, '(なし)')
ORDER BY nick;
  nick  | cnt
--------+-----
        |   1
 (なし) |   2
 ao     |   1
(3 rows)

GROUP BY は NULL どうしを1つのグループにまとめます(GROUP BY nickname でも NULL のグループは1つ)。COALESCE は、そのグループに表示用の名前を付ける役割です。空文字(id = 3)は NULL とは別のグループになるので、まとめたいなら COALESCE(NULLIF(nickname, ''), '(なし)') にします。

ORDER BYでNULLの位置を決める(DBで既定の位置が違う)

ORDER BY points DESC としたとき、NULL がどこに並ぶかは DB で違います。

DB ORDER BY points DESC の順 末尾に固定する書き方
PostgreSQL 16.15 NULL, NULL, 120, 80(NULL が最大扱い) ORDER BY points DESC NULLS LAST
MySQL 8.0.46 120, 80, NULL, NULL(NULL が最小扱い) ORDER BY points IS NULL, points DESC(NULLS LAST は構文エラー)
SQLite 3.45.1 120, 80, NULL, NULL(NULL が最小扱い) ORDER BY points DESC NULLS LAST

3つの DB で同じ順にしたいなら、ORDER BY COALESCE(points, -1) DESC, id のように NULL を代替値で置き換えて並べます。 3つとも id 1、3、2、4 の順になりました。

文字列連結・計算・更新でNULLが伝播するとき

文字列の連結

NULL を含む連結は、書き方と DB の組み合わせで結果が変わります。

書き方 MySQL 8.0.46 PostgreSQL 16.15 SQLite 3.45.1
'a' || NULL NULL(既定では || は論理和) NULL NULL
CONCAT('a', NULL) NULL a a
CONCAT_WS('-', 'a', NULL, 'b') a-b a-b a-b
-- PostgreSQL:|| は NULL が1つでも混ざると全体が NULL
SELECT id,
       name || ' (' || nickname || ')'               AS a,
       name || ' (' || COALESCE(nickname, '-') || ')' AS b,
       CONCAT_WS(' / ', name, nickname, email)        AS d
FROM members ORDER BY id;
 id |     a     |     b     |              d
----+-----------+-----------+------------------------------
  1 | Aoki (ao) | Aoki (ao) | Aoki / ao / aoki@example.com
  2 | NULL      | Baba (-)  | Baba / baba@example.com
  3 | Chiba ()  | Chiba ()  | Chiba /
  4 | NULL      | Doi (-)   | Doi
(4 rows)

どの DB でも同じ結果にしたいときは、連結する各項目を COALESCE(列, '') で包むか、NULL を飛ばす CONCAT_WS を使います。 GREATEST(1, NULL) も MySQL では NULL、PostgreSQL では 1 と、関数ごとに NULL の扱いが違います。NULL が混ざる可能性のある列は、関数に渡す前に COALESCE でそろえておくと DB による差が出ません。

足し算と UPDATE での加算

NULL を含む計算は結果が NULL になります。項目ごとに COALESCE で 0 へ置き換えてから足します。

SELECT id, points + visits AS raw, COALESCE(points, 0) + COALESCE(visits, 0) AS total
FROM members ORDER BY id;
 id | raw  | total
----+------+-------
  1 |  124 |   124
  2 | NULL |     0
  3 |   82 |    82
  4 | NULL |     0
(4 rows)

UPDATE での加算も同じです。PostgreSQL 16 で、COALESCE あり・なしを並べて実行しました。

UPDATE members SET points = COALESCE(points, 0) + 10 WHERE id IN (1, 2) RETURNING id, points;
 id | points
----+--------
  1 |    130
  2 |     10
(2 rows)

UPDATE members SET points = points + 10 WHERE id IN (3, 4) RETURNING id, points;
 id | points
----+--------
  3 |     90
  4 |   NULL
(2 rows)

points + 10 では、もともと NULL だった id = 4 は NULL のままです。UPDATE 文の書き方は関連記事「SQL UPDATE文の書き方|複数列・別テーブル・CASE・DB別構文の早見表」で扱っています。

NOT NULL の列へ入れるとき

NOT NULL の列に NULL を入れようとするとエラーになります。INSERT ... SELECT で元の列に NULL があるなら、COALESCE で既定値に置き換えてから入れます。

INSERT INTO t_ins (id, qty) SELECT id, visits FROM members;
ERROR:  null value in column "qty" of relation "t_ins" violates not-null constraint
DETAIL:  Failing row contains (4, null).

INSERT INTO t_ins (id, qty) SELECT id, COALESCE(visits, 0) FROM members;
INSERT 0 4

列を NOT NULL にするか、既定値をどう決めるかは関連記事「NULL許可の設計方針|NOT NULLとデフォルト値の決め方早見表」で扱っています。

NULLIFの使い方(ゼロ除算の回避・空文字をNULLにする)

NULLIF は COALESCE の逆向きで、「2つの引数が等しければ NULL、違えば1つ目の値」を返す関数です。 「この値は“なし”として扱いたい」ときに使います。CASE で書くと CASE WHEN a = b THEN NULL ELSE a END と同じ意味で、MySQL・SQL Server のドキュメントもこの等価な CASE 式を載せています。

SELECT NULLIF(1, 1) AS a, NULLIF(1, 2) AS b, NULLIF('a', '') AS c, NULLIF(NULL, 1) AS d, NULLIF(1, NULL) AS e;
  a   | b | c |  d   | e
------+---+---+------+---
 NULL | 1 | a | NULL | 1
(1 row)

ゼロ除算を避ける:x / NULLIF(y, 0)

0 で割ったときの動きは DB によって違います。

DB SELECT 1 / 0 の結果
PostgreSQL 16.15 ERROR: division by zero
MySQL 8.0.46 NULL(Warning 1365 Division by 0。既定の sql_mode では INSERT に使うと ERROR 1365 (22012): Division by 0)
SQLite 3.45.1 NULL

割る数を NULLIF(列, 0) で包むと、0 のときは「NULL で割る」ことになり、結果が NULL になってエラーを避けられます。

SELECT id, points, visits, points / NULLIF(visits, 0) AS per_visit
FROM members ORDER BY id;
 id | points | visits | per_visit
----+--------+--------+-----------
  1 |    120 |      4 |        30
  2 |   NULL |      0 |      NULL
  3 |     80 |      2 |        40
  4 |   NULL |   NULL |      NULL
(4 rows)

visits が 0 の id = 2 もエラーにならず NULL が返っています。MySQL では 30.0000 のように小数で表示され、MySQL でエラーになった INSERT INTO z VALUES (10 / 0) も、10 / NULLIF(0, 0) と書くと NULL が入りました。結果を NULL ではなく 0 で表示したい場合は、外側を COALESCE で包みます。

SELECT id, COALESCE(points / NULLIF(visits, 0), 0) AS per_visit FROM members ORDER BY id;
 id | per_visit
----+-----------
  1 |        30
  2 |         0
  3 |        40
  4 |         0
(4 rows)

空文字も未設定として扱う:COALESCE(NULLIF(列, ''), '代替値')

最初の例で見たように、空文字は NULL ではないので COALESCE をすり抜けます。NULLIF(列, '') で空文字を NULL に変えてから COALESCE で置き換えるのが定番の形です。

SELECT id, COALESCE(NULLIF(nickname, ''), '未設定') AS nick FROM members ORDER BY id;
+----+-----------+
| id | nick      |
+----+-----------+
|  1 | ao        |
|  2 | 未設定    |
|  3 | 未設定    |
|  4 | 未設定    |
+----+-----------+

NULL の id = 2・4 と、空文字の id = 3 が、すべて「未設定」になりました(3つの DB で同じ結果)。空白だけの値もまとめて扱いたいときは、TRIM で前後の空白を取り除いてから渡します。COALESCE(NULLIF(TRIM(' '), ''), 'empty') は、3つの DB すべてで empty を返しました。

NULLIFの比較は照合順序の影響を受ける

NULLIF の「等しいかどうか」は、通常の = と同じ規則で判定されます。大文字と小文字を区別しない設定の DB では結果が変わります。

DB NULLIF('a', 'A') の結果
MySQL 8.0.46(既定の照合順序 utf8mb4_0900_ai_ci) NULL('a' と 'A' を同じと判定)
PostgreSQL 16.15 a
SQLite 3.45.1 a(既定は BINARY 照合)

戻り値の型は「1つ目の引数と同じ」と各 DB のドキュメントにあります。PostgreSQL では = の比較のために1つ目が昇格することがあり、pg_typeof(NULLIF(1, 2.2)) は numeric でした。MySQL のドキュメントには「引数が等しくないとき expr1 を2回評価する」とあるので、1つ目に重い関数やサブクエリを置くのは避けます。

CASE式との等価性と、COALESCE・CASE・IFNULLのどれを使うか

COALESCE は CASE 式の省略形です。Oracle のリファレンスでは COALESCE(expr1, expr2) が CASE WHEN expr1 IS NOT NULL THEN expr1 ELSE expr2 END と等価、3つ以上は入れ子の CASE と等価と説明されています。実際に並べて実行すると同じ結果になります。

SELECT id,
       COALESCE(nickname, email, 'guest') AS c,
       CASE WHEN nickname IS NOT NULL THEN nickname
            WHEN email    IS NOT NULL THEN email
            ELSE 'guest' END             AS cs
FROM members ORDER BY id;
 id |        c         |        cs
----+------------------+------------------
  1 | ao               | ao
  2 | baba@example.com | baba@example.com
  3 |                  |
  4 | guest            | guest
(4 rows)

「NULL かどうか」だけで値を切り替えるなら COALESCE のほうが短く、意図も伝わります。CASE が要るのは、NULL 以外の条件(値の範囲、別の列の状態、NULL でないときに別の値を返す NVL2 相当)で分岐するときです。

状況 選ぶもの 理由
NULL を代替値に置き換えるだけ COALESCE 5つの DB で共通。引数を増やせる
候補が3つ以上ある(列A → 列B → 固定値) COALESCE IFNULL・NVL は2引数。入れ子にすると読みにくい
その DB 専用で、既存コードが IFNULL/NVL で統一されている そのまま IFNULL/NVL/ISNULL 動きは同じ。混在させない
SQL Server で、代替値の型を第1引数に合わせたい・サブクエリを1回だけ評価したい ISNULL ドキュメントの差(型・評価回数)
NULL でなければ A、NULL なら B(NVL2) CASE WHEN a IS NOT NULL THEN A ELSE B END NVL2 は Oracle だけ
値や範囲で分岐する CASE COALESCE では書けない
特定の値(0・空文字)を NULL にしたい NULLIF COALESCE の逆向き
flowchart TD
  A["NULL のときの値を決めたい"] --> B{"条件は NULL かどうかだけ?"}
  B -- "いいえ(値や範囲で分岐)" --> C["CASE 式"]
  B -- "はい" --> D{"NULL でないときは元の値?"}
  D -- "いいえ(別の値を返す)" --> C
  D -- "はい" --> E{"複数の DB で動かす?"}
  E -- "はい" --> F["COALESCE"]
  E -- "いいえ" --> G{"候補は3つ以上?"}
  G -- "はい" --> F
  G -- "いいえ" --> H["COALESCE か DB の関数(IFNULL・NVL・ISNULL)"]

CASE 式の書き方は関連記事「SQL CASE式の使い方|用途別コピペ集とDB比較・実行結果つき」で扱っています。

WHERE句にCOALESCEを書くとインデックスが効かない(EXPLAINの実物)

絞り込みの条件で列を COALESCE で包むと、その列のインデックスが使われなくなります。100 万行の items(cat 列にインデックス。10 万行が NULL、cat = 5 は 100 行)で確かめました。

PostgreSQL 16:Seq Scan 39 ms → Index Scan 0.4 ms

EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF) SELECT * FROM items WHERE COALESCE(cat, 0) = 5;
 Gather (actual rows=100 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Seq Scan on items (actual rows=33 loops=3)
         Filter: (COALESCE(cat, 0) = 5)
         Rows Removed by Filter: 333300
 Planning Time: 0.136 ms
 Execution Time: 39.073 ms

EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF) SELECT * FROM items WHERE cat = 5;
 Bitmap Heap Scan on items (actual rows=100 loops=1)
   Recheck Cond: (cat = 5)
   Heap Blocks: exact=100
   ->  Bitmap Index Scan on items_cat (actual rows=100 loops=1)
         Index Cond: (cat = 5)
 Planning Time: 0.071 ms
 Execution Time: 0.437 ms

COALESCE で包んだほうは Seq Scan(表全体を読む)で 100 万行から 100 行を探し、包まないほうはインデックスで 100 行を直接読みました。

MySQL 8.0:type ALL 1156 ms → ref 1.4 ms

EXPLAIN SELECT * FROM items WHERE COALESCE(cat, 0) = 5;
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | items | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 997750 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+

EXPLAIN SELECT * FROM items WHERE cat = 5;
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | items | NULL       | ref  | items_cat     | items_cat | 5       | const |  100 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+

EXPLAIN ANALYZE SELECT * FROM items WHERE COALESCE(cat, 0) = 5;
-> Filter: (coalesce(items.cat,0) = 5)  (cost=101011 rows=997750) (actual time=0.259..1156 rows=100 loops=1)
    -> Table scan on items  (cost=101011 rows=997750) (actual time=0.192..977 rows=1e+6 loops=1)

EXPLAIN ANALYZE SELECT * FROM items WHERE cat = 5;
-> Index lookup on items using items_cat (cat=5)  (cost=35 rows=100) (actual time=1.16..1.4 rows=100 loops=1)

type: ALL(全行走査)で 1,156 ms、type: ref(インデックス参照)で 1.4 ms でした。IFNULL(cat, 0) = 5 も同じく ALL になります。EXPLAIN の各列の読み方は関連記事「MySQL EXPLAINの見方|type・key・rows・Extra早見表」で扱っています。

直し方:条件を書き直すか、式インデックスを作る

直し方 書き方 向いている場面
列をそのまま比べる WHERE cat = 5 代替値と一致させたい行が無いとき(0 の行を探すなら cat = 0 OR cat IS NULL)
NULL の行だけ探す WHERE cat IS NULL(COALESCE(cat, 0) = 0 の代わり) PostgreSQL は IS NULL でもインデックスを使う(Bitmap Index Scan を確認)
式インデックスを作る PostgreSQL CREATE INDEX items_cat_co ON items ((COALESCE(cat, 0)));、MySQL 8.0.13 以降も同じ構文 SQL を変えにくいとき。インデックスが1つ増える

式インデックスを作ったあと、同じ SQL が PostgreSQL では Bitmap Index Scan on items_cat_co(Execution Time: 0.180 ms)、MySQL では type: ref、key: items_cat_co(actual time=0.501..0.681)になりました。

SELECT 句(表示)の COALESCE は、速度にほとんど影響しません。 SELECT id, COALESCE(cat, 0) FROM items WHERE id BETWEEN 1 AND 1000 は Index Scan のまま 0.197 ms でした。気にするのは WHERE 句や JOIN の条件に書くときです。関数で包む以外のパターンは関連記事「SQLのインデックスが効かない10のパターンと確認方法」で扱っています。

引数の評価(必要なところまでしか計算しない)

COALESCE は、NULL でない値が見つかった時点で、それより右の引数を計算しません(短絡評価)。時間のかかる関数を2番目に置いて確かめました。

SQL かかった時間 2番目の引数
MySQL SELECT COALESCE(1, SLEEP(2)) 0.01 秒 計算されなかった
MySQL SELECT COALESCE(NULL, SLEEP(2)) 2.01 秒 計算された
MySQL SELECT IFNULL(1, SLEEP(2)) 0.01 秒 計算されなかった
PostgreSQL SELECT COALESCE(1, (SELECT 1 FROM pg_sleep(2))) 0.089 ms 計算されなかった
PostgreSQL SELECT COALESCE(NULL, (SELECT 1 FROM pg_sleep(2))) 2002.363 ms 計算された

エラーになる式でも同じです。

-- PostgreSQL:2番目はゼロ除算になる式だが、1番目が NULL でなければ計算されない
SELECT COALESCE(points, 1 / (points - points)) FROM members WHERE id = 1;   -- 120
SELECT COALESCE(points, 1 / (visits - visits)) FROM members WHERE id = 2;   -- ERROR:  division by zero

-- SQLite:abs(-9223372036854775808) は桁あふれになる式
SELECT COALESCE(1, abs(-9223372036854775808));      -- 1
SELECT COALESCE(NULL, abs(-9223372036854775808));   -- Runtime error: integer overflow
DB COALESCE の評価 根拠
MySQL 8.0.46 必要なところまで(IFNULL も同じ) SLEEP で実測
PostgreSQL 16.15 必要なところまで pg_sleep とゼロ除算で実測。ドキュメントにも「CASE と同様、結果を決めるのに必要な引数だけ評価する」と明記
SQLite 3.45.1 必要なところまで(IFNULL も同じ) 桁あふれの式で実測
Oracle 短絡評価を行うとリファレンスに明記 ドキュメント
SQL Server COALESCE は CASE 式に展開され、サブクエリを含む引数は2回評価される。ISNULL は1回 ドキュメント

SQL Server については、Microsoft Learn の COALESCE のページに「COALESCE((サブクエリ), 1) はサブクエリが2回評価され、READ COMMITTED 分離レベルでは1回目と2回目で結果が変わりうる」という説明があります。対処として、ISNULL を使うか SNAPSHOT 分離レベルにする方法が挙げられています。

エラー・つまずきと直し方(原文・原因・直し方)

エラー文・症状(原文) 原因 直し方
PostgreSQL ERROR: function ifnull(unknown, unknown) does not exist/function nvl(unknown, unknown) does not exist IFNULL・NVL は PostgreSQL にない COALESCE(a, b)
MySQL ERROR 1305 (42000): FUNCTION db.NVL does not exist/SQLite no such function: NVL NVL は Oracle の関数 COALESCE(a, b)
MySQL ERROR 1582 (42000): Incorrect parameter count in the call to native function 'ISNULL' MySQL の ISNULL は1引数の判定関数 置き換えは COALESCE(a, b) か IFNULL(a, b)
MySQL ERROR 1582 (42000): Incorrect parameter count in the call to native function 'IFNULL' IFNULL に3つ以上渡した COALESCE(a, b, c)
SQLite Parse error: near "ISNULL": syntax error SQLite の ISNULL は後置の演算子 判定は a ISNULL、置き換えは COALESCE/IFNULL
SQLite Parse error: wrong number of arguments to function COALESCE() 引数が1個以下 引数を2個以上にする
PostgreSQL ERROR: invalid input syntax for type integer: "none" 数値の列に文字列の代替値 COALESCE(CAST(列 AS TEXT), 'none')
PostgreSQL ERROR: COALESCE types integer and text cannot be matched 型の違う列・式を並べた 片方を CAST でそろえる
SQL Server エラー 4127 At least one of the arguments to COALESCE must be an expression that is not the NULL constant. NULL 定数だけを並べた 列か CAST(NULL AS INT) のように型付きの NULL を使う
COALESCE したのに空欄のまま 値が NULL ではなく空文字 COALESCE(NULLIF(列, ''), '代替値')
MySQL で並び順や比較がおかしい 代替値の型が列と違い、文字列として扱われた 代替値の型を列に合わせる(0、-1)
合計が NULL で表示される 0 件、または全行が NULL COALESCE(SUM(列), 0)(SUM(COALESCE(...)) では直らない)
平均が想定より大きい AVG が NULL の行を分母から外している 全件で割るなら AVG(COALESCE(列, 0))
LEFT JOIN の件数が 1 になる COUNT(*) が NULL の行も数えた COUNT(相手の列)
<> の条件で NULL の行が出てこない NULL との比較は不明になる COALESCE(列, '') <> '値'、IS DISTINCT FROM(PostgreSQL)、<=>(MySQL)、IS NOT(SQLite)
PostgreSQL ERROR: division by zero 割る数が 0 割られる数 / NULLIF(割る数, 0)
WHERE に書いたら遅くなった 列を関数で包んでインデックスが使われない 列をそのまま比べる形に直す。式インデックス

やってはいけない書き方と、やり直し方

書き方 起きること 代わりに
UPDATE t SET col = COALESCE(col, 0) で保存データの NULL を 0 に上書き 「未入力」と「0 と入力した」の区別が消え、COMMIT 後は戻せない 表示や集計のときに COALESCE する。列を変えるなら前に別テーブルへコピー
代替値の型を列に合わせない PostgreSQL はエラー、MySQL は文字列として動き続ける 列の型に合わせる。文字で出すなら列を CAST
WHERE・JOIN の条件で列を COALESCE で包む インデックスが効かず全行走査(100 万行で 39 ms/1,156 ms) 条件を書き直す。式インデックス
SUM(COALESCE(x, 0)) で 0 件対策 0 件のとき NULL のまま COALESCE(SUM(x), 0)
AVG(COALESCE(x, 0)) を無条件に使う NULL の人も分母に入り、平均が下がる 求めたい分母に合わせて AVG(x) と使い分ける
SQL Server の COALESCE にサブクエリを渡す サブクエリが2回評価され、結果が変わりうる(ドキュメント) ISNULL にするか、サブクエリを外に出す

NULL を 0 で上書きしたときの「戻せない」様子を、PostgreSQL 16 でトランザクションの中で確かめました。

BEGIN;
UPDATE members SET points = COALESCE(points, 0);
UPDATE 4
SELECT COUNT(*) AS was_null FROM members WHERE points IS NULL;
 was_null
----------
        0
(1 row)
ROLLBACK;

更新後は「もともと NULL だった行」を探す手がかりが無くなります。この例は COMMIT 前なので ROLLBACK で戻りましたが、COMMIT 後はバックアップからの復元になります。NULL の性質と比較の落とし穴は関連記事「SQLのNULLの扱い|COALESCE・NULLIFと落とし穴12選」で、NULL との比較で行が消える BETWEEN の例は関連記事「SQL BETWEENの使い方|日付の最終日が漏れる原因と直し方・DB別早見表」で扱っています。

よくある質問

Q. sql coalesce とは何をする関数ですか? A. 引数を左から順に見て、最初の NULL でない値を返す標準 SQL の関数です。COALESCE(points, 0) で NULL を 0 に置き換えられ、引数は3つ以上並べられます。MySQL・PostgreSQL・SQLite・SQL Server・Oracle のすべてで使えます。読み方は「コアレス」です。

Q. mysql coalesce と IFNULL はどちらを使えばよいですか? A. MySQL 8.0.46 で動きを比べると、どちらも1つ目が NULL でなければ2つ目を計算せず、結果も同じでした。違いは、IFNULL が2引数だけなのに対し COALESCE は3つ以上並べられること、他の DB(PostgreSQL など)では IFNULL が動かないことです。新しく書くなら COALESCE が移植しやすく、既存コードが IFNULL で統一されているならそのままでも動きは変わりません。

Q. mysql nvl はありますか? A. ありません。SELECT NVL(NULL, 'x') は ERROR 1305 (42000): FUNCTION db.NVL does not exist になります。COALESCE(a, b) か IFNULL(a, b) に書き換えます。Oracle の NVL2(a, b, c) は IF(a IS NOT NULL, b, c) か CASE 式で書きます。

Q. sqlite isnull はどう書きますか? A. SQLite の ISNULL は関数ではなく、points ISNULL と後ろに置く判定の演算子(IS NULL と同じ)です。ISNULL(points, 0) と書くと Parse error: near "ISNULL": syntax error になります。NULL を置き換えるなら IFNULL(points, 0) か COALESCE(points, 0) です。

Q. coalesce 使い方の基本形は? A. COALESCE(列, 代替値) です。代替値には固定値のほか、別の列や式、サブクエリも書けます。代替値の型は列の型に合わせます(数値の列に文字列を渡すと PostgreSQL ではエラー、MySQL では文字列として扱われます)。

Q. ifnull nvl 違いは何ですか? A. 働きは同じ「2引数で、1つ目が NULL なら2つ目を返す」ですが、使える DB が違います。IFNULL は MySQL・SQLite、NVL は Oracle、ISNULL(2引数)は SQL Server です。型の決まり方も違い、Oracle の NVL は1つ目が文字なら2つ目をその型に変換し、SQL Server の ISNULL は1つ目の型に合わせます。どの DB でも動く書き方は COALESCE です。

Q. coalesce と isnull の違いは? A. SQL Server では、ISNULL は2引数で戻り値が第1引数の型、COALESCE は引数がいくつでも使えて戻り値は優先順位のいちばん高い型です。評価回数も違い、ISNULL は1回、COALESCE はサブクエリを含む引数が2回評価されることがあります。MySQL の ISNULL は1引数の判定関数で、置き換えには使えません。

Q. COALESCE の引数がすべて NULL だとどうなりますか? A. NULL が返ります。最後の引数に固定値を置いておけば NULL にはなりません。SQL Server で NULL 定数だけを並べるとエラー 4127 になるので、少なくとも1つは列か型付きの値にします。

Q. NULL と空文字と 0 は何が違いますか? A. NULL は「値が入っていない」、空文字は「長さ 0 の文字列という値が入っている」、0 は「0 という数値が入っている」状態です。COALESCE が置き換えるのは NULL だけなので、空文字や 0 も対象にしたいときは NULLIF(列, '') や NULLIF(列, 0) で NULL に変えてから渡します。Oracle だけは空文字を NULL として扱うとリファレンスに書かれています。

Q. WHERE 句に COALESCE を書いてもよいですか? A. 動きますが、その列のインデックスが使われません。100 万行で測ると、PostgreSQL は 39 ms → 0.4 ms、MySQL は 1,156 ms → 1.4 ms の差になりました。cat = 5 のように列をそのまま比べるか、式インデックスを作ります。

まとめ

  • COALESCE は「最初の NULL でない値」を返す標準 SQL の関数。5つの DB で共通に使え、引数は2個以上いくつでも並べられる。左から順に見て、見つかった時点で右側は評価しない
  • IFNULL は MySQL・SQLite、NVL・NVL2 は Oracle、置き換えの ISNULL は SQL Server。MySQL の ISNULL は1引数の判定関数、PostgreSQL・SQLite の ISNULL は後置の演算子で、意味が違う
  • 代替値の型は列の型に合わせる。PostgreSQL は COALESCE types integer and text cannot be matched で止まり、MySQL は文字列として動き続けて並び順や比較が変わる。文字で出すなら列を CAST
  • 集計は NULL を飛ばす。0 件の SUM は NULL になるので COALESCE(SUM(列), 0)。全件で平均するなら AVG(COALESCE(列, 0))。LEFT JOIN の件数は COUNT(相手の列)
  • NULLIF は逆向きの関数。x / NULLIF(y, 0) でゼロ除算を避け、COALESCE(NULLIF(列, ''), '未設定') で空文字もまとめて置き換える
  • WHERE・JOIN の条件で列を COALESCE で包むとインデックスが効かない(100 万行で PostgreSQL 39 ms → 0.4 ms、MySQL 1,156 ms → 1.4 ms)。条件を書き直すか、式インデックスを作る
  • 保存データの NULL を 0 で上書きすると、未入力だった事実は戻らない。置き換えは表示・集計のときに行う

参考・出典

確認日はいずれも 2026年10月16日です。

  • PostgreSQL 16 Documentation「9.18. Conditional Expressions(COALESCE・NULLIF・GREATEST and LEAST)」:https://www.postgresql.org/docs/16/functions-conditional.html
  • PostgreSQL 16 Documentation「10.5. UNION, CASE, and Related Constructs」:https://www.postgresql.org/docs/16/typeconv-union-case.html
  • PostgreSQL 16 Documentation「11.7. Indexes on Expressions」:https://www.postgresql.org/docs/16/indexes-expressional.html
  • MySQL 8.0 Reference Manual「Comparison Functions and Operators(COALESCE・ISNULL・<=>)」:https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html
  • MySQL 8.0 Reference Manual「Flow Control Functions(IF・IFNULL・NULLIF)」:https://dev.mysql.com/doc/refman/8.0/en/flow-control-functions.html
  • MySQL 8.0 Reference Manual「CREATE INDEX Statement(Functional Key Parts)」:https://dev.mysql.com/doc/refman/8.0/en/create-index.html
  • SQLite「Built-In Scalar SQL Functions(coalesce・ifnull・nullif・iif)」:https://www.sqlite.org/lang_corefunc.html
  • Microsoft Learn「COALESCE (Transact-SQL)」:https://learn.microsoft.com/en-us/sql/t-sql/language-elements/coalesce-transact-sql
  • Microsoft Learn「ISNULL (Transact-SQL)」:https://learn.microsoft.com/en-us/sql/t-sql/functions/isnull-transact-sql
  • Microsoft Learn「NULLIF (Transact-SQL)」:https://learn.microsoft.com/en-us/sql/t-sql/language-elements/nullif-transact-sql
  • Microsoft Learn「Database Engine events and errors (4000 to 4999)」:https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors-4000-to-4999
  • Oracle Database 23ai SQL Language Reference「COALESCE」:https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/COALESCE.html
  • Oracle Database 23ai SQL Language Reference「NVL」:https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/NVL.html
  • Oracle Database 23ai SQL Language Reference「NVL2」:https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/NVL2.html
  • Oracle Database 23ai SQL Language Reference「Nulls」:https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Nulls.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「SQL COALESCEの使い方|IFNULL・NVL・ISNULL対応表と実行結果」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?