データモデル設計ガイド

NULL 許容の判断基準 — NOT NULL を基本に

NULL の「未知・非適用・空」を区別し、3 値論理の罠を避ける設計指針。

NULL 許容の判断基準 — NOT NULL を基本に diagram

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 列)
sql
-- 良い例: デフォルトは 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 NULL
  • NOT 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 を空文字として扱う歴史的挙動