NULL 許容の判断基準 — NOT NULL を基本に
NULL の「未知・非適用・空」を区別し、3 値論理の罠を避ける設計指針。
NULL は「値がない」ではない
SQL の NULL は「値が入っていない」ではなく、「値が不明 (unknown)」を表すマーカーです。空文字 '' や 0 とは別の概念で、比較演算子では NULL = NULL すら TRUE ではなく UNKNOWN を返します。これが SQL の 3 値論理 (three-valued logic) の根本です。
そのため WHERE col = NULL は 1 行も返しません。必ず IS NULL / IS NOT NULL を使います。JOIN 条件や集計 (COUNT(col) は NULL を除外) でも挙動が変わるため、NULL 許容列はクエリ全体に影響します。
NULL が意味しうる 3 つのケース
同じ NULL でも、業務上の意味は次のいずれかです。混在させるとデータが腐ります。
- 未知 (unknown): 値は存在するが、まだ入力されていない(例: ユーザーの生年月日がまだ未登録)
- 非適用 (not applicable): そもそも値を持ち得ない(例: 法人顧客の「性別」列)
- 意図的な欠損 (optional): 任意項目で、空のままでよい(例: 備考欄)
「非適用」は特に危険で、列設計自体を見直すサインです。法人・個人が 1 テーブルに混ざっているなら、サブタイプ分離(customers + person_customers + corp_customers)や CHECK 制約で排他性を表現する方が健全です。
NOT NULL を基本に、NULL は意識的に許可する
設計ルールは簡単です: すべての列を NOT NULL にして、必要な列だけ意識的に NULL 許容にする。デフォルトを逆にすると、「なぜこの列は NULL 許容なのか」を誰も説明できないまま負債化します。
NULL を避けるテクニック:
- 文字列で「未入力」を表すなら
DEFAULT ''+NOT NULLにする(空文字と NULL を混在させない) - 数値のデフォルト値が明確なら
DEFAULT 0+NOT NULL - 状態の区別が必要なら、列ではなく別テーブルに分離する(例: 退職日が未設定 → 在職中を表す別の
employment_status列)
-- 良い例: デフォルトは NOT NULL
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
bio TEXT NOT NULL DEFAULT '', -- 空文字で統一
deleted_at TIMESTAMPTZ NULL -- ここは意図的に NULL 許容
);NULL と 3 値論理の罠
NULL 許容列は、クエリ全体の挙動を狂わせます。代表的なワナ:
WHERE col != 'A'は NULL の行を取りこぼす(NULL != 'A' は UNKNOWN)。NULL も除外したいならWHERE col != 'A' OR col IS NULLNOT IN (SELECT ...)はサブクエリ結果に NULL が 1 つでも混ざると全行 UNKNOWN になって何も返らない。NOT EXISTSを使うCOUNT(col)は NULL を除外、COUNT(*)は全行数。集計結果がずれる原因になるUNIQUE制約では NULL は重複チェックの対象外(PostgreSQL / MySQL)。複数の NULL 行を許可してしまう
これらは SQL 仕様通りの挙動ですが、直感と反するためバグを生みやすい部分です。NULL 許容列を増やすほどクエリのレビュー負荷が上がることを覚えておきます。
RDBMS ごとの NULL 挙動の違い
NULL 周りには RDBMS 差分があります。
- UNIQUE と NULL: SQL 標準では「複数の NULL を許可」。PostgreSQL / MySQL / SQLite / Oracle は標準どおり複数 NULL を許可。SQL Server は「NULL を 1 つだけ許可」と非標準寄り。移植時に要注意
- ORDER BY の NULL 位置: PostgreSQL / Oracle は ASC で NULL が最後、MySQL / SQL Server は先頭。明示するなら
ORDER BY col NULLS FIRST/NULLS LAST(PostgreSQL / Oracle / SQLite 3.30+ / SQL Server 2022+ がサポート。MySQL 8.x は未対応でIS NULLと併用して代用) - 文字列連結: 標準 SQL では
'a' || NULLは NULL。Oracle のみ NULL を空文字として扱う歴史的挙動
