- SQL
SQLのNULLの扱い|COALESCE・NULLIFと落とし穴12選
SQLのNULLは「値がない・不明」を表す特別な印で、= では比較できず、IS NULL で判定し、COALESCE で別の値に置き換えます。 NULL を含む比較の結果は TRUE でも FALSE でもない「不明(UNKNOWN)」になり、WHERE は TRUE の行だけを返します。これが NULL にまつわる意外な結果のほとんどの原因です。
この記事では、3値論理の真理値表と、NOT IN で0件になる・COUNT の数が合わない・ソート位置がDBで違うなどの落とし穴12個を、PostgreSQL 16.13 と MySQL 8.0.46 の両方で実行した結果で比べます。NVL・IFNULL・ISNULL・COALESCE の DB 別対応表、NULLIF で0除算を避けるコピペSQL、NULL を増やさない設計の指針までまとめました。
SQLのNULLの扱い 早見表
「やりたいこと → 書き方」をこの表で探してください。 どのDBでも使える標準SQLを先に書いています。
| やりたいこと | 書き方(標準SQL) | 注意 |
|---|---|---|
| NULLかどうか判定 | col IS NULL / col IS NOT NULL |
col = NULL は常に0件 |
| NULLを別の値に置き換え | COALESCE(col, 0) |
最初の非NULLを返す。引数はいくつでも |
| 特定の値をNULLにする | NULLIF(col, '') |
2つが等しければNULL |
| 空文字とNULLをまとめて置き換え | COALESCE(NULLIF(col, ''), '(未登録)') |
表示用の定番 |
| 0除算を避ける | a / NULLIF(b, 0) |
分母0でNULLになる |
| NULL同士も等しいとみなして比較 | a IS NOT DISTINCT FROM b |
MySQLは a <=> b |
| 「〜以外」にNULLも含める | col <> 'x' OR col IS NULL |
<> だけではNULL行が落ちる |
| 別表に存在しない行(NULLに強い) | NOT EXISTS (SELECT 1 ...) |
NOT IN はサブクエリにNULLがあると0件 |
| 全行を数える / 値のある行を数える | COUNT(*) / COUNT(col) |
COUNT(col) はNULLを数えない |
| 合計が0件でも0を返す | COALESCE(SUM(col), 0) |
全部NULLだと SUM はNULL |
| NULLを0として平均 | AVG(COALESCE(col, 0)) |
AVG(col) はNULLを除いて平均 |
| NULLを最後に並べる | ORDER BY col NULLS LAST |
MySQLは ORDER BY col IS NULL, col |
| NULLを含めて一意にする | UNIQUE NULLS NOT DISTINCT(PG15〜) |
通常のUNIQUEはNULLを複数許す |
| NULLを無視して文字列連結 | CONCAT_WS(',', a, b) |
|| はNULLがあると全体がNULL |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「SQLのNULLの扱い|COALESCE・NULLIFと落とし穴12選」
NULLとは何か:3値論理の真理値表
NULL との比較(= <> > など)や計算(+ など)の結果は NULL(不明)になります。 SQL の条件は TRUE・FALSE・UNKNOWN の3つの値をとり、これを3値論理と呼びます。
SELECT NULL = NULL AS eq, NULL <> NULL AS ne, NULL IS NULL AS isnull, 1 + NULL AS plus;
SELECT (NULL AND FALSE) AS n_and_f, (NULL AND TRUE) AS n_and_t,
(NULL OR TRUE) AS n_or_t, (NULL OR FALSE) AS n_or_f, (NOT NULL) AS not_n;
実行結果(PostgreSQL 16.13。NULLを (null) と表示する設定):
eq | ne | isnull | plus
--------+--------+--------+--------
(null) | (null) | t | (null)
n_and_f | n_and_t | n_or_t | n_or_f | not_n
---------+---------+--------+--------+--------
f | (null) | t | (null) | (null)
MySQL 8.0.46 でも同じ結果で、表示が NULL / 1 / 0 になるだけです。
| A | B | A AND B | A OR B | NOT A |
|---|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE | FALSE |
| TRUE | UNKNOWN | UNKNOWN | TRUE | FALSE |
| TRUE | FALSE | FALSE | TRUE | FALSE |
| UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN |
| UNKNOWN | FALSE | FALSE | UNKNOWN | UNKNOWN |
| FALSE | FALSE | FALSE | FALSE | TRUE |
覚え方は「UNKNOWN は TRUE か FALSE のどちらか分からない値」です。 UNKNOWN AND FALSE は、どちらでも FALSE になるので FALSE。UNKNOWN OR TRUE は、どちらでも TRUE になるので TRUE。それ以外は UNKNOWN のままです。
WHERE・ON・HAVING は TRUE の行だけを残し、UNKNOWN は FALSE と同じく捨てます。 一方、CHECK 制約は「FALSE でなければ通す」ので、NULL は CHECK を通過します。
検証に使うテーブル
以降の実行結果は、次のテーブルで PostgreSQL 16.13 と MySQL 8.0.46 の両方で確かめたものです。
CREATE TABLE members (id int PRIMARY KEY, name varchar(20), email varchar(50), points int, dept_id int);
INSERT INTO members VALUES
(1, '佐藤', 'sato@example.com', 100, 10),
(2, '鈴木', NULL, NULL, 20),
(3, '高橋', '', 50, NULL),
(4, '田中', NULL, NULL, 10);
CREATE TABLE depts (id int PRIMARY KEY, name varchar(20));
INSERT INTO depts VALUES (10, '開発'), (20, '営業'), (30, '総務');
落とし穴1:= NULL は常に0件
WHERE email = NULL は、どの行でも UNKNOWN になるので1件も返りません。エラーにもならない点に気をつけます。
SELECT id, name FROM members WHERE email = NULL; -- 直す前
SELECT id, name FROM members WHERE email IS NULL ORDER BY id; -- 直した後
id | name
----+------
(0 rows)
id | name
----+------
2 | 鈴木
4 | 田中
落とし穴2:<>(〜以外)でNULLの行が消える
「メールが sato@example.com 以外の人」を <> で探すと、NULL の2人は結果に入りません。
SELECT id, name FROM members WHERE email <> 'sato@example.com' ORDER BY id;
id | name
----+------
3 | 高橋
NULL の行も含めたいときは OR email IS NULL を足すか、email IS DISTINCT FROM 'sato@example.com'(MySQLは NOT (email <=> 'sato@example.com'))と書きます。
落とし穴3:NOT IN のサブクエリにNULLがあると0件
NOT IN のリストに1つでも NULL があると、結果は必ず0件になります。 「どの会員もいない部署」を探す例です。members.dept_id には NULL(高橋さん)が入っています。
-- 直す前:0件になる
SELECT id FROM depts WHERE id NOT IN (SELECT dept_id FROM members);
-- 直した後①:NOT EXISTS に書き換える(おすすめ)
SELECT d.id FROM depts d
WHERE NOT EXISTS (SELECT 1 FROM members m WHERE m.dept_id = d.id);
-- 直した後②:サブクエリからNULLを除く
SELECT id FROM depts WHERE id NOT IN (SELECT dept_id FROM members WHERE dept_id IS NOT NULL);
id
----
(0 rows)
id
----
30
id
----
30
MySQL 8.0.46 も同じく、直す前は Empty set、直した後は 30 でした。
なぜ0件になるのか: 30 NOT IN (10, 20, NULL, 10) は 30 <> 10 AND 30 <> 20 AND 30 <> NULL AND ... と同じ意味です。30 <> NULL が UNKNOWN なので、全体は TRUE になれません。リテラルでも同じで、WHERE id NOT IN (20, NULL) は両DBとも0件でした。一方 IN (10, NULL) は id = 10 が TRUE になれば OR で TRUE になるので、10 が返ります。
| 書き方 | サブクエリにNULLがあるとき | おすすめ度 |
|---|---|---|
NOT IN (SELECT col ...) |
0件になる | 列が NOT NULL と保証できるときだけ |
NOT IN (SELECT col ... WHERE col IS NOT NULL) |
正しく動く | 条件を忘れやすい |
NOT EXISTS (SELECT 1 ... WHERE 結合条件) |
正しく動く | 基本はこれ |
LEFT JOIN ... WHERE 右.id IS NULL |
正しく動く | 結合の形で書きたいとき |
落とし穴4:COUNT(*) と COUNT(列) で数が違う
COUNT(*) は行数、COUNT(列) はその列が NULL でない行数を数えます。
SELECT COUNT(*) AS cnt_all, COUNT(points) AS cnt_points, COUNT(email) AS cnt_email,
COUNT(DISTINCT dept_id) AS distinct_depts
FROM members;
cnt_all | cnt_points | cnt_email | distinct_depts
---------+------------+-----------+----------------
4 | 2 | 2 | 2
COUNT(email) は空文字 '' の高橋さんを数えます(NULL ではないため)。COUNT(DISTINCT dept_id) も NULL を除くので 2 です。
LEFT JOIN で件数を数えるときは、右側の列を COUNT します。 COUNT(*) だと、相手がいない部署も1と数えられます。
SELECT d.name, COUNT(*) AS cnt_star, COUNT(m.id) AS cnt_id
FROM depts d LEFT JOIN members m ON m.dept_id = d.id
GROUP BY d.id, d.name ORDER BY d.id;
name | cnt_star | cnt_id
------+----------+--------
開発 | 2 | 2
営業 | 1 | 1
総務 | 1 | 0
落とし穴5:AVG は NULL を除いて平均する
AVG(points) は NULL の行を分母に入れません。 NULL を0点として平均したいなら COALESCE で置き換えます。
SELECT SUM(points) AS sum_p, AVG(points) AS avg_p, AVG(COALESCE(points, 0)) AS avg_p0, MAX(points) AS max_p
FROM members;
| 結果 | sum_p | avg_p | avg_p0 | max_p |
|---|---|---|---|---|
| PostgreSQL 16 | 150 | 75.0000000000000000 | 37.5000000000000000 | 100 |
| MySQL 8.0 | 150 | 75.0000 | 37.5000 | 100 |
どちらが正しいかは業務の意味で決まります。 「未受験は平均に入れない」なら AVG(points)、「未獲得は0ポイント」なら AVG(COALESCE(points, 0)) です。
落とし穴6:SUM は全部NULL(または0件)だと NULL
SUM の対象がすべて NULL、または対象行が0件のとき、結果は 0 ではなく NULL です。 アプリで数値として受け取る場合は、NULLの考慮が必要になります。
SELECT SUM(points) AS sum_null, COALESCE(SUM(points), 0) AS sum0, COUNT(points) AS c
FROM members WHERE id IN (2, 4);
sum_null | sum0 | c
----------+------+---
(null) | 0 | 0
MySQL も NULL | 0 | 0 でした。集計結果を返すときは COALESCE(SUM(...), 0) を定番にしましょう。 なお COUNT は0件でも 0 を返します。
落とし穴7:GROUP BY では NULL が1つのグループになる
比較では NULL 同士は等しくありませんが、GROUP BY・DISTINCT では NULL はまとめて1グループになります。
SELECT dept_id, COUNT(*) FROM members GROUP BY dept_id ORDER BY dept_id;
| PostgreSQL 16 | MySQL 8.0 |
|---|---|
| 10→2、20→1、NULL→1(最後) | NULL→1(最初)、10→2、20→1 |
グループのまとまり方は同じで、並ぶ位置が違います(次の落とし穴)。
落とし穴8:NULLのソート位置がDBで違う
PostgreSQL と Oracle は NULL を「最大の値」として扱い、MySQL と SQL Server は「最小の値」として扱います。
SELECT id, points FROM members ORDER BY points;
SELECT id, points FROM members ORDER BY points DESC;
| 並べ方 | PostgreSQL 16 の結果(id順) | MySQL 8.0 の結果(id順) |
|---|---|---|
ORDER BY points |
3(50), 1(100), 2(NULL), 4(NULL) | 2(NULL), 4(NULL), 3(50), 1(100) |
ORDER BY points DESC |
2(NULL), 4(NULL), 1(100), 3(50) | 1(100), 3(50), 2(NULL), 4(NULL) |
どのDBでも同じ順にしたいときは、NULL の位置を明示します。
-- PostgreSQL / Oracle(標準SQL)
SELECT id, points FROM members ORDER BY points DESC NULLS LAST;
-- MySQL(NULLS FIRST/LAST 構文がないため、IS NULL の真偽で先に並べる)
SELECT id, points FROM members ORDER BY points IS NULL, points DESC;
どちらも結果は 1(100), 3(50), 2(NULL), 4(NULL) でした。ORDER BY points IS NULL は NULL でない行が 0、NULL の行が 1 になるので、NULL が後ろに行きます。
落とし穴9:UNIQUE制約でもNULLは複数入る
標準SQLでは NULL 同士は「等しい」と判定されないため、UNIQUE 列に NULL を何行でも入れられます。
CREATE TABLE u1 (id int, code varchar(10) UNIQUE);
INSERT INTO u1 VALUES (1, NULL), (2, NULL); -- PostgreSQL・MySQL とも成功(2行入る)
PostgreSQL 15 以降は NULLS NOT DISTINCT で、NULL も1つまでに制限できます。
CREATE TABLE u2 (id int, code varchar(10) UNIQUE NULLS NOT DISTINCT);
INSERT INTO u2 VALUES (1, NULL);
INSERT INTO u2 VALUES (2, NULL);
INSERT 0 1
ERROR: duplicate key value violates unique constraint "u2_code_key"
DETAIL: Key (code)=(null) already exists.
| DB | UNIQUE列に NULL を複数 |
|---|---|
| PostgreSQL | 入る(NULLS NOT DISTINCT で1つまでにできる) |
| MySQL(InnoDB) | 入る |
| Oracle | 入る(単一列の場合) |
| SQL Server | 1つまで(複数許すならフィルター付き一意インデックス WHERE col IS NOT NULL) |
落とし穴10:空文字 '' と NULL は別物(Oracleは同じ)
PostgreSQL・MySQL・SQL Server では ''(長さ0の文字列)と NULL は別の値です。
SELECT '' IS NULL AS empty_is_null, LENGTH('') AS len_empty, LENGTH(NULL) AS len_null;
empty_is_null | len_empty | len_null
---------------+-----------+----------
f | 0 | (null)
MySQL も 0 | 0 | NULL でした。このため WHERE email IS NULL では高橋さん('')は見つからず、未登録をまとめて探すには email IS NULL OR email = '' が必要です(実行すると id 2, 3, 4 が返りました)。
Oracle は '' を NULL として扱います。 Oracle から移行するときは、'' を入れていた箇所が NULL として動くことを前提に条件を見直します。
落とし穴11:文字列連結でNULLが混ざると全体がNULL
|| による連結は、1つでも NULL があると結果全体が NULL になります。 関数によって扱いが違うので表で確認します。
SELECT id, name || '(' || email || ')' AS pipe, -- PostgreSQL
CONCAT(name, '(', email, ')') AS concat,
CONCAT_WS(',', name, email) AS concat_ws
FROM members ORDER BY id;
| id | ||(PG) |
CONCAT(PG) |
CONCAT(MySQL) |
CONCAT_WS(両方) |
|
|---|---|---|---|---|---|
| 1 | sato@example.com | 佐藤(sato@example.com) | 佐藤(sato@example.com) | 佐藤(sato@example.com) | 佐藤,sato@example.com |
| 2 | NULL | NULL | 鈴木() | NULL | 鈴木 |
| 3 | '' | 高橋() | 高橋() | 高橋() | 高橋, |
(id=4 は id=2 と同じ結果。PostgreSQL は上のSQLそのまま、MySQL は CONCAT と CONCAT_WS を実行)
- PostgreSQL の
CONCATは NULL を無視し、MySQL のCONCATは NULL を返します。 同じ関数名でも挙動が違います。 CONCAT_WSは両DBとも NULL を飛ばします。 区切り付きの連結に便利です。- MySQL の
||は既定で論理 OR です。SELECT 'a' || 'b'は0になり、警告'|| as a synonym for OR' is deprecatedが出ました(sql_mode にPIPES_AS_CONCATを入れると連結になります)。
どのDBでも確実にするなら、COALESCE(email, '') で先に置き換えてから連結します。
落とし穴12:NULL 同士を「等しい」として比較したい
a = b は両方 NULL のとき UNKNOWN なので、JOIN や変更検知で NULL 同士が一致しません。 標準SQLの IS NOT DISTINCT FROM を使います。
-- メールが同じ会員のペア(NULL同士も同じとみなす)
SELECT m1.id, m2.id
FROM members m1 JOIN members m2
ON m1.email IS NOT DISTINCT FROM m2.email AND m1.id < m2.id;
id | id
----+----
2 | 4
= で結合すると0件でした。MySQL は IS NOT DISTINCT FROM に対応しておらず(8.0.46 で ERROR 1064)、代わりに NULL 安全等価演算子 <=> を使います。
SELECT NULL <=> NULL AS a, 1 <=> NULL AS b, NULL = NULL AS c; -- MySQL
+---+---+------+
| a | b | c |
+---+---+------+
| 1 | 0 | NULL |
+---+---+------+
| DB | NULL同士も等しいとみなす比較 |
|---|---|
| PostgreSQL | a IS NOT DISTINCT FROM b |
| MySQL | a <=> b |
| SQL Server | a IS NOT DISTINCT FROM b(2022以降) |
| Oracle | DECODE(a, b, 1, 0) = 1(DECODE は NULL 同士を一致とみなす) |
NVL・IFNULL・ISNULL・COALESCE・NULLIF のDB別対応表
迷ったら、どのDBでも使える COALESCE と NULLIF を選べば移植しやすくなります。
| 関数 | PostgreSQL | MySQL | SQL Server | Oracle | 意味 |
|---|---|---|---|---|---|
COALESCE(a, b, ...) |
○ | ○ | ○ | ○ | 最初の非NULL(標準SQL) |
NULLIF(a, b) |
○ | ○ | ○ | ○ | a = b なら NULL(標準SQL) |
NVL(a, b) |
― | ― | ― | ○ | a が NULL なら b |
NVL2(a, b, c) |
― | ― | ― | ○ | a が非NULLなら b、NULLなら c |
IFNULL(a, b) |
― | ○ | ― | ― | a が NULL なら b(SQLiteにもあり) |
ISNULL(a, b) |
― | ― | ○ | ― | a が NULL なら b |
ISNULL(a) |
― | ○ | ― | ― | 判定(NULLなら1)。SQL Server と意味が違う |
IS [NOT] DISTINCT FROM |
○ | ― | ○(2022〜) | ― | NULL安全な比較 |
<=> |
― | ○ | ― | ― | NULL安全な等価 |
NULLS FIRST / LAST |
○ | ― | ― | ○ | NULLの並び順 |
MySQL の IFNULL(email, '(未登録)') と ISNULL(email) は、id=2・4 でそれぞれ (未登録)・1 を返しました。
COALESCE の型に注意(PostgreSQL)
COALESCE の引数は同じ型にそろえる必要があります。 PostgreSQL では数値列に文字を渡すとエラーになります。
SELECT COALESCE(points, 'なし') FROM members;
ERROR: invalid input syntax for type integer: "なし"
SELECT COALESCE(points::text, 'なし') AS p FROM members ORDER BY id; -- 100, なし, 50, なし
MySQL は型を自動で変換し、COALESCE(points, 'なし') がそのまま 100, なし, 50, なし を返しました。移植性を考えるなら、明示的にキャストしておくと安全です。
コピペで使えるNULL対策SQL集
よく使う形をまとめました。 いずれも上のテーブルで PostgreSQL と MySQL の両方で実行した形です(|| を除く)。
-- 1. NULLを既定値に置き換えて表示
SELECT id, COALESCE(email, '(未登録)') AS email, COALESCE(points, 0) AS points FROM members;
-- 2. 空文字もNULLとみなして表示(id 2,3,4 が (未登録) になる)
SELECT id, COALESCE(NULLIF(email, ''), '(未登録)') AS email_disp FROM members;
-- 3. 0除算を避ける(分母が0ならNULL)
SELECT 100 / NULLIF(0, 0) AS safe_div;
-- 4. 構成比(合計が0やNULLでもエラーにならない)
SELECT id, points, 100.0 * points / NULLIF(SUM(points) OVER (), 0) AS pct FROM members;
-- 5. 集計が0件でも0を返す
SELECT COALESCE(SUM(points), 0) AS total FROM members WHERE dept_id = 30;
-- 6. 「〜以外」にNULLも含める
SELECT id FROM members WHERE email <> 'sato@example.com' OR email IS NULL;
-- 7. 相手がいない行を探す
SELECT d.id FROM depts d WHERE NOT EXISTS (SELECT 1 FROM members m WHERE m.dept_id = d.id);
-- 8. CASE で NULL を明示的に分類
SELECT id, CASE WHEN points IS NULL THEN 'unknown'
WHEN points > 60 THEN 'high'
ELSE 'low' END AS lvl
FROM members;
0除算の挙動もDBで違います。 PostgreSQL の SELECT 10 / 0 は ERROR: division by zero で止まり、MySQL は NULL を返して警告 1365 Division by 0 を出しました(既定の sql_mode の SELECT の場合)。NULLIF(分母, 0) を使えば、どちらでも NULL にそろいます。構成比(4)は PostgreSQL で 66.6666666666666667 / (null) / 33.3333333333333333 / (null)、MySQL で 66.66667 / NULL / 33.33333 / NULL でした。
GREATEST(1, NULL, 3) も違いがあり、PostgreSQL は NULL を無視して 3、MySQL は NULL を返しました。
NULLを増やさない設計の指針
NULL の扱いで迷う場面は、設計の段階でかなり減らせます。
| 指針 | 具体的には | ねらい |
|---|---|---|
| 既定は NOT NULL | 列を作るときはまず NOT NULL、必要な列だけ NULL を許す | 3値論理を考える箇所を減らす |
| NULLの意味を1つに決める | 「未入力」「該当なし」「不明」を混ぜない。必要なら状態列を別に持つ | 集計や条件の解釈がぶれない |
| 既定値を使う | 数量は DEFAULT 0、フラグは DEFAULT false |
NULLと0の区別が必要ない列は0で持つ |
| 空文字とNULLを混ぜない | 入力時に NULLIF(trim(x), '') で片方にそろえる |
IS NULL OR = '' が不要になる |
| 外部キー列は意図してNULLにする | 「未割り当て」を表すなら NULL、必須なら NOT NULL | NOT IN の事故を防ぐ |
| 一意性の要件を確認 | NULL を1つまでにしたいなら NULLS NOT DISTINCT など |
DBごとの違いを吸収 |
レビュー用チェックリスト
| チェック | 見るところ |
|---|---|
= NULL / <> NULL がないか |
IS NULL / IS NOT NULL に |
NOT IN (サブクエリ) の列が NULL になりえないか |
NOT EXISTS に |
<> の条件で NULL 行を含めるべきか |
OR col IS NULL |
SUM の結果を数値として使っていないか |
COALESCE(SUM(...), 0) |
AVG の分母の意味は合っているか |
NULL除外か0扱いか |
LEFT JOIN の件数が COUNT(*) になっていないか |
COUNT(右.id) |
| ORDER BY で NULL の位置を決めているか | NULLS LAST など |
| 連結に NULL が混ざらないか | COALESCE / CONCAT_WS |
よくある質問
Q. COALESCE と IFNULL・NVL・ISNULL はどれを使えばよいですか?
A. COALESCE をおすすめします。標準SQLなのでどのDBでも動き、3つ以上の引数も渡せます。IFNULL は MySQL、NVL は Oracle、ISNULL は SQL Server 固有で、MySQL の ISNULL(a) は判定関数なので意味が違います。
Q. NOT IN と NOT EXISTS はどちらを使うべきですか?
A. サブクエリの列に NULL が入る可能性があるなら NOT EXISTS です。NOT IN は NULL が1つあるだけで0件になります。列が NOT NULL なら結果は同じです。
Q. NULL と空文字、どちらで「未入力」を表すべきですか?
A. どちらか1つに決めることが大切です。多くの場合は NULL に統一し、入力時に NULLIF(trim(x), '') で空文字を NULL に変えます。Oracle では空文字が NULL になるので、移植を考えるなら NULL に統一しておくと扱いがそろいます。
Q. WHERE col IS NULL にインデックスは効きますか?
A. PostgreSQL・MySQL の B-tree インデックスは NULL も索引に含めるので、IS NULL の検索にも使えます。Oracle の単一列 B-tree は全列 NULL の行を索引に入れない点が違います。
まとめ
- NULLは「不明」。
=ではなくIS NULL、NULL同士の一致はIS NOT DISTINCT FROM(MySQLは<=>) で比べます。 - 比較の結果は TRUE・FALSE・UNKNOWN の3値。WHERE は TRUE だけを残すので、
<>やNOT INで行が消えます。 NOT INはサブクエリにNULLがあると0件。NOT EXISTSに書き換えるのが基本です。COUNT(*)とCOUNT(列)、AVGの分母、全NULLのSUMは NULL を押さえ、COALESCE(SUM(...), 0)を定番にします。- ソート位置・
CONCAT・GREATEST・0除算はDBで挙動が違うので、NULLS LAST・COALESCE・NULLIFで明示します。 - 置き換えは移植しやすい
COALESCEとNULLIFを使い、NVL・IFNULL・ISNULL はDB固有と覚えます。 - 設計では NOT NULL を既定にし、NULLの意味を1つに決めると、落とし穴の多くを避けられます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「SQLのNULLの扱い|COALESCE・NULLIFと落とし穴12選」
株式会社RJCでは、SQLの書き方やテーブル設計の工夫を、プロジェクトの中で仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。