SQLのNULLって、「空っぽの値」だと思われがちなんですよね。

でも正体は「不明」という第3の真理値を連れてくる存在です。真と偽の二択で考えていると、条件式が静かに裏切ってきます。今回は、「NOT INで全行消えた」のような実害の大きいものを8つ集めました。1つでも知らなかったら儲けものです。

① NULL = NULL は、真になりません

すべての出発点です。

SELECT * FROM users WHERE nickname = NULL;  -- 1行も返らない

NULLは「不明」なので、不明と不明を比べた結果も「不明(UNKNOWN)」です。真でも偽でもない、第3の答え。

そしてWHERE句は、真の行だけを通します。不明は通りません。

だからNULLの検索は IS NULL という専用構文になる。SQLが真・偽・不明の三値論理で動いている——この1点から、今日の話が全部始まります。

② NOT IN のリストにNULLが混ざると、全行消えます

三値論理の、いちばん派手な事故です。

SELECT * FROM orders
WHERE user_id NOT IN (SELECT user_id FROM blacklist);
-- blacklistのuser_idに1つでもNULLがあると → 結果0行

NOT INは「リストのどれとも等しくない」の判定ですが、NULLとの比較が「不明」になるため、全体が真になれません。結果、1行も通らない。

エラーは出ず、黙って0行。サブクエリの列にNULLが入った日から突然発症します。対策はNOT EXISTSを使うか、サブクエリでNULLを除外することです。

③ 「<> 'A'」に、NULLの行は含まれません

直感に反する定番が、これです。

SELECT * FROM users WHERE plan <> 'premium';

「premium以外の全員」のつもりですよね。でもplanがNULLの行は、返ってきません。NULL <> 'premium' が「不明」だからです。

「AとA以外に分ければ全部のはず」という二分法の直感が、NULLの存在で三分割になっている。「無料プランの人数が合わない」の犯人は、だいたいここにいます。

④ 集計関数は、NULLを黙って除外します

もうひとつの静かな罠です。AVG・SUM・COUNT(列名)は、NULLの行を計算に入れません。

-- 点数: 80, 90, NULL の3人
SELECT AVG(score) FROM exams;   -- → 85(2人の平均!)
SELECT COUNT(*) FROM exams;     -- → 3
SELECT COUNT(score) FROM exams; -- → 2

「未受験を0点として平均したい」なら、明示的に変換が要ります。COUNT(*)とCOUNT(列)が別物なのも、同じ理屈です。

集計のズレ調査では、まず「分母は何人か」から疑うんです。

⑤ Oracleでは、空文字がNULLになります

DB移行で踏む、有名な地雷です。

Oracleは、長さ0の文字列('')をNULLとして扱います。「空文字を入れたつもりが、NULLが入っている」わけです。

PostgreSQLやMySQLでは、空文字とNULLは別物。つまり WHERE col = '' が、Oracleでは絶対にヒットしません(NULLとの比較になるので)。

「同じデータ、同じSQLなのに、DBを替えたら挙動が変わった」——空文字まわりは、移行チェックリストの常連なんです。

⑥ ORDER BYでのNULLの位置は、DBで違います

並び替えたとき、NULLは先頭に来るのか、末尾に来るのか。

DB昇順(ASC)でのNULLの位置
PostgreSQL・Oracle末尾
MySQL・SQL Server先頭

「新しい順に並べたら、日付未設定の行が一番上に居座る」という画面バグは、この差の産物です。

対策は、位置を明示すること。PostgreSQLなどでは ORDER BY col DESC NULLS LAST と書けます。NULLの居場所も、仕様として決める対象なんです。

⑦ 「NULLも含めて等しいか」を、1発で比較できます

NULLを考慮した比較を、こう書いていませんか。

WHERE a = b OR (a IS NULL AND b IS NULL)

これ、専用の演算子があります。

WHERE a IS NOT DISTINCT FROM b   -- 標準SQL / PostgreSQLなど
WHERE a <=> b                    -- MySQL

「NULL同士なら真、片方だけNULLなら偽」という、人間の直感どおりの比較です。差分検出や変更チェックのSQLが、劇的に読みやすくなります。

知っている人が少ないぶん、レビューで書くと一目置かれる演算子です。

⑧ NULLは、「不明」と「該当なし」の二役を背負わされています

最後は、設計の話です。

電話番号がNULL——それは「聞いていない(不明)」なのか、「持っていない(該当なし)」なのか。NULLという1つの値に、意味の違う2つの状態を背負わせているのが、多くの事故の根なんです。

だから設計の定石はこうなります。

  • 意味を区別したいなら、NULLに頼らず状態を別の列や値で表現する
  • 意味が1つに決まる列は、NOT NULL制約とデフォルト値で、そもそもNULLを入れない

NULLは便利な値ではなく、「考えることを先送りした印」——そう見ると、テーブル設計の景色が変わります。

番外編:手元で効く小ネタ

NULLを値に変える・値をNULLに変える
COALESCE(col, 0) は「NULLなら0」。逆にNULLIF(a, b) は「aとbが等しければNULL」。ゼロ除算回避の NULLIF(分母, 0) は定番の護身術です。

UNIQUE制約とNULLの関係も、DBで違います
「NULLは重複扱いしない(複数OK)」のDBが多数派ですが、SQL Serverのように1つしか許さないDBもあります。一意制約の列にNULLを許すときは、確認が要ります。

三値論理は、SQLの発明ではありません
真・偽・不明の論理学は、1920年ごろにポーランドの論理学者ウカシェヴィチらが研究していました。SQLは「現実のデータには不明がある」と認めた、正直な言語なんですね。

RJCでも、NULLと向き合っています

RJCが携わる保険や金融のシステムは、「未入力」と「該当なし」の区別が審査や計算の分岐になる世界です。⑧の設計論は、そのまま設計書のレビュー項目なんですよね。

こういう仕組みの話を、Techチームで共有しながら開発しています。

よくいただくご質問

Q. まずどれから押さえるべきですか?
A. ②のNOT INかなと思います。事故が派手で、しかも明日書くSQLで即防げるんですよね。

Q. NULLは使わない方がいいのですか?
A. ゼロにはできませんが、減らせます。⑧のとおりNOT NULL+デフォルト値を基本にして、NULLを「意図して許した列」だけに絞るのが定石です。

Q. 調査のコツはありますか?
A. 「件数が合わない」ときは、③と④を疑ってください。WHERE条件と集計のどちらかで、NULLが静かに抜けていることが本当に多いんです。

Q. 未経験からでも、分かるようになりますか?
A. なります。「NULLは空っぽではなく『わからない』」——この一文だけ握っていれば、①〜④は全部その応用で読めます。

「わからない」を、わかって扱う

今日の8つに共通していたのは、これでした。

NULLの事故は、エラーではなく「静かに行が消える」「静かに数字がズレる」形で来ます。三値論理を知らないと、原因に辿り着けません。

「この条件、NULLの行はどっちに落ちる?」——レビューでこれを問える人が、集計の信頼を守るんですよね。

みなさんがNULLで踏んだ事故、よかったら教えてください。

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で一緒に開発しながら、 成長を楽しみませんか?