🧩

「NOT NULL制約 + DEFAULT制約」と「NULL許容カラム」の違い

に公開

はじめに

Rails でアプリケーションを開発していると、カラムを追加するときに次のようなマイグレーションを書く場面があります。

# 例1: NOT NULL制約 + DEFAULT制約
add_column :users, :nickname, :string, default: "", null: false

# 例2: NULL許容カラム
add_column :users, :nickname, :string, null: true

この記事では 文字列カラム(string/text)を前提 に、「NOT NULL制約 + DEFAULT制約」と「NULL許容カラム」の違いを整理します。

私はこれまでの開発で、null: false, default: "" を使う場面をあまり選んできた記憶がありません。基本的にはデフォルトの null: true(NULL許容)で設計してきました。

その背景にはいくつかの理由があります。

  • null: true を選びがちな理由

    • Rails のバリデーション(presence: true など)で、未入力判定をアプリ層で行う設計に慣れている
    • 「未入力(NULL)」と「空文字("")」を区別したいケースが多く、データの意味が表現しやすい
    • DB制約を最小化して、アプリの表現力と移行容易性を優先しがち

一方で、null: false, default: "" には次の利点があります。

  • null: false, default: ""を選ぶと嬉しいこと

    • クエリで IS NULL を考えなくてよくなり、検索条件がシンプルになりやすい
    • 文字列カラムが常に空文字であることが DB により保証されるため、保存後のデータ扱いが一定になる
    • ログや内部補助カラムのように「NULL が混入すると後工程が面倒」な場所で扱いやすい

つまり、どちらが正しいというよりも 「そのカラムで何を表現したいか(意味論)」 によって選び方が変わります。


1. 用語の整理

NOT NULL 制約

  • カラムに NULL を格納できないようにする制約
  • 常に値が存在することを保証する

DEFAULT 制約

  • INSERT 時に値が指定されなかった場合に DB が自動で格納する値
  • 例: DEFAULT '' → 値を指定しないと空文字が入る
    ※ 明示的に NULL を送ると DEFAULT は適用されません(NOT NULL なら違反でエラー)

NULL 許容カラム (nullable column)

  • NULL を格納できるカラム
  • SQL標準ではカラムはデフォルトで NULL 許容
  • 対義語は「NOT NULL制約」

2. 「NOT NULL制約 + DEFAULT制約」の特徴

nickname VARCHAR(255) NOT NULL DEFAULT '';

Rails では次のように書きます。

add_column :users, :nickname, :string, default: "", null: false
  • 保存時に値が指定されない場合、DB が空文字 "" を補完する
  • 明示的に nil を挿入しようとすると、NOT NULL 違反でエラーになる

例(挙動のタイミング)

u = User.new
u.nickname        # => nil   # まだDBに保存していないため、モデル上はnilのまま
u.save!           # DBがDEFAULTを適用
u.nickname        # => ""    # PG等はRETURNINGにより即時反映。確実に知りたければ u.reload.nickname

メリット

  • 保存後のデータは空文字で揃い、IS NULL を考慮しなくてよい
  • 「文字列である」前提の後続処理が書きやすい

デメリット

  • 未入力 (NULL) と空文字 ("") を区別できない(意味論が薄まる)
  • ユニーク制約と組み合わせると、空文字の一意性で詰まりがち(後述)

3. NULL許容カラムの特徴

nickname VARCHAR(255) NULL;

Rails では次のように書きます。

add_column :users, :nickname, :string, null: true
  • 値が指定されなければ NULL(Rails では nil として扱う)

メリット

  • 未入力 (NULL) と空文字 ("") を区別でき、データの意味が正確になる

デメリット

  • クエリや集計で NULL を特別扱いする必要がある(後述)
  • アプリ層でも nil を考慮した分岐が増える

4. Railsにおける影響

バリデーション

Rails の presence: truenil""(および空白のみの文字列)を不許可とします。

class User < ApplicationRecord
  validates :nickname, presence: true
end
  • NULL許容カラム

    • nil → NG
    • "" → NG
  • NOT NULL + DEFAULT("")

    • 保存前nil の可能性がある(User.new.nickname
    • 保存時nil を明示的に渡すと NOT NULL 違反
    • 保存後"" が入るため、以降の判定で nil? は基本不要

👉 必須入力のカラムには、DB 側の DEFAULT "" + NOT NULL に頼るより、アプリ層のバリデーションを正しく設計する方が安全


クエリでの違い

-- NOT NULL + DEFAULT
SELECT * FROM users WHERE nickname IS NULL;  -- 常に0件

-- NULL許容
SELECT * FROM users WHERE nickname IS NULL;  -- 未入力ユーザーを取得できる

Rails スコープ例:

User.where(nickname: nil)    # => 結果が出るのは NULL 許容のみ
User.where(nickname: "")     # => 空文字を保存している行のみ取得
User.where(nickname: [nil, ""])  # => 両方を明示的に扱いたい場合

5. ベストプラクティス

「NOT NULL制約 + DEFAULT制約」を選ぶべきケース

  • 値の有無を区別する必要がない(ログ/内部補助カラム等)
  • 後工程で NULL が混ざるとパイプラインが壊れやすい

「NULL許容カラム」を選ぶべきケース

  • 未入力と空文字を区別したい
  • ユーザー入力やビジネス的意味を持つフィールド(プロフィール、ニックネーム、備考等)

6. まとめ

  • NOT NULL制約 + DEFAULT制約
    → 実装はシンプルだが、未入力と空文字を区別できず意味が曖昧になる
  • NULL許容カラム
    → データの意味を正しく表現できるが、クエリ/集計/アプリで NULL 対応が必要

Rails では「データの意味を表現すること」を優先し、ユーザー入力を扱うカラムは NULL 許容を基本にするのが安全です。


7. Rails DSLとSQL名称の対応表

RailsマイグレーションDSL SQL表現 (例: PostgreSQL / MySQL) 正式名称
null: false NOT NULL NOT NULL制約
default: "" DEFAULT '' DEFAULT制約
null: false, default: "" NOT NULL DEFAULT '' NOT NULL制約 + DEFAULT制約
null: true(明示/既定) NULL または省略 NULL許容カラム (nullable)

8. 補足①:なぜ「NULLが混ざると面倒」なのか(具体例)

8.1 比較・条件式が冗長になる

NULL は「未知」を表すため、=<> の比較でヒットしません。

-- 空か未入力をまとめて拾いたい
-- (NULL を含めるには OR 句や COALESCE が必要)
WHERE nickname = '' OR nickname IS NULL
-- もしくは(ただし関数適用はインデックスが効きにくい)
WHERE COALESCE(nickname, '') = ''

8.2 集計関数に含まれない

COUNT(nickname) は NULL を数えません。意図しないズレが生じます。

SELECT COUNT(nickname) FROM users;  -- NULL はカウント対象外
SELECT COUNT(*)       FROM users;   -- 全行をカウント

8.3 ユニーク制約との相性

多くのDBでは NULL は互いに異なる とみなされるため、UNIQUE(email) かつ email が NULL 許容だと、NULL の行を複数作れてしまいます。
「未設定は何件でもOK/設定されている時だけ一意」を狙うなら、PostgreSQL の 部分ユニークインデックス が有効です。

-- email が NOT NULL の行だけ一意にする
CREATE UNIQUE INDEX users_email_unique_not_null
  ON users (email) WHERE email IS NOT NULL;

8.4 関数適用・OR でインデックスが効きづらい

COALESCE(column, '') のように列へ関数を当てると 非SARGable になり、索引が効かないケースが出ます。
OR column IS NULL もプラン次第で効率が落ちます。
→ 設計段階で「NULL を許すかどうか」「クエリの書き方」を決めておくと後の性能トラブルを避けやすいです。

8.5 文字列関数の落とし穴

LENGTH(nickname)LOWER(nickname) は、引数が NULL なら結果も NULL。
意図せず WHERE 句が UNKNOWN になって除外されることがあります。


9. 補足②:「空文字を保証していても nil を考慮すべき」具体ケース

9.1 保存前のインスタンス

DB の DEFAULT は 保存時に適用されます。
生成直後は nil のままです(Rails のモデル属性にアプリ側デフォルトを定義していない場合)。

u = User.new
u.nickname.nil?      # => true
u.save!              # => DB が DEFAULT '' を適用
u.nickname           # => "" (PG 等は即時反映。確実なら reload)

対策:モデル層でアプリ側デフォルトを付けると一貫します。

class User < ApplicationRecord
  attribute :nickname, :string, default: ""  # 保存前でも "" になる
end

9.2 リクエストパラメータで null が明示される

JSON API などで {"nickname": null} が送られてきた場合、保存前は nil です。
DB が NOT NULL ならバリデーションで弾くか、アプリで正規化してから保存しましょう。

# 例:setter で正規化(空白は削除、空は nil)
def nickname=(v)
  super(v.is_a?(String) ? v.strip.presence : v)
end

9.3 既存データの移行漏れ

null: true から null: false, default: "" に変更した際、既存行の NULL を "" に更新しておかないと、思わぬところで nil 前提のコードが落ちます。

def up
  change_column_default :users, :nickname, ""
  execute "UPDATE users SET nickname = '' WHERE nickname IS NULL"
  change_column_null :users, :nickname, false
end

10. 補足③:empty?blank? の正しい使い分け

Rails では blank? / present? が頻出しますが、Ruby の empty? と混同しがちです。
要点を先に言うと

  • 「ちょっとでも実体のある文字列か?」を判定したいblank? / present? を使う
  • 「正確に空文字(長さ0)か?」を判定したいempty? を使う(ただし String に対してのみ

10.1 代表的な挙動

nil? empty? blank? present?
nil true (エラー) true false
"" false true true false
" " false false true false
"a" false false false true
  • empty?長さが0 かだけを見る(空白 " "empty? ではない
  • blank?nil / "" / 空白のみ とみなす(Rails の拡張)

10.2 実務での指針

  • 未入力を弾く・入力有無の判定nickname.present? / nickname.blank?

    • 空白だけの入力 " " も空として扱える
  • 正確に「空文字と等しいか」を見たいnickname == "" または nickname.empty?(String に限定)

  • nil と空文字を同一視したい(String でない可能性あり)nickname.to_s.empty?

    • ※ ただし nil と "" の区別が消えるので、意味論が必要なら避ける

10.3 例:ビューやバリデーションでの使い分け

# 1) 表示:未入力ならプレースホルダ
nickname = user.nickname
display = nickname.present? ? nickname : "(未設定)"

# 2) バリデーション:実体のある文字だけ許可
validates :nickname, presence: true

# 3) 厳密に空文字を検出(DBが NOT NULL+DEFAULT ""、空文字だけを特別扱いしたい)
if user.nickname.empty?
  # 空文字にだけ適用する特別処理
end

11. 設計のヒント:正規化ポリシーを「モデル層」で決める

  • NULL許容を選ぶなら、setter で空文字を nil に寄せると検索・集計が安定します。

  • NOT NULL + DEFAULT "" を選ぶなら、attribute :..., default: "" を併用して 保存前から空文字を一貫させると nil 分岐が減ります。

  • ユニーク制約と絡むフィールド(例:メールアドレス等)は、

    • 「未設定は何件でもOK/設定されている場合のみ一意」→ NULL許容 + 部分ユニーク(PostgreSQL)
    • 「未設定も含め常に一意」→ NOT NULL + DEFAULT "" + UNIQUE(ただし空文字は1件しか持てない)

12. 注意点(DB差異とマイグレーション)

  • DB により挙動や最適プランは異なります(PostgreSQL / MySQL / SQLite 等)。
  • Rails の add_column は既定で NULL 許容change_column_null で後から制約を加えるパターンもあります。
  • 既存データの移行(UPDATE ... WHERE ... IS NULL)を 必ず伴わせること。

おわりに

NULL を許すか、空文字で統一するかは 意味論・クエリの簡潔さ・性能・移行容易性 のトレードオフです。
本記事の指針をベースに、カラムごとに 一貫した正規化ポリシー を決めて運用すると、長期的な保守が格段に楽になります。

Discussion