「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制約を最小化して、アプリの表現力と移行容易性を優先しがち
- Rails のバリデーション(
一方で、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: true
は nil
と ""
(および空白のみの文字列)を不許可とします。
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
になって除外されることがあります。
nil
を考慮すべき」具体ケース
9. 補足②:「空文字を保証していても 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
null
が明示される
9.2 リクエストパラメータで 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
empty?
と blank?
の正しい使い分け
10. 補足③: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