学習することができます。
学ぶ目的
新しい項目を「必須」で足したくなる
会員登録で、新しく「生まれ年」を聞くことにしました。全員に入力してほしいので、データベースにも必須(NOT NULL)の列として足したくなります。
ここで困るのは、もう会員がいることです。既存の会員は生まれ年を入力していません。
いきなり必須の列を足すと、失敗する
会員が2人いる表に、必須の列をデフォルトなしで足してみます(PostgreSQL 16 で確認)。
ALTER TABLE members ADD COLUMN birth_year integer NOT NULL;
ERROR: column "birth_year" of relation "members" contains null values
既存の2人には値が無いので、「必ず値がある」という約束を守れず、失敗します。
怖いのは、開発環境の空の表では、同じ文が成功することです。手元で通ったのに、本番に出した瞬間に失敗する。データが貯まっている本番だけで起きる、典型的な「後から困ること」です。
3段階で足す:広げる → 埋める → 締める
既存のデータと両立させるには、変更を3つに分けます。
| 段階 | やること | 英語での呼び名 |
|---|---|---|
| 広げる | null を許して列を足す。古いコードも新しいコードも動く | expand |
| 埋める | 既存の行に値を入れる | migrate |
| 締める | 全行が埋まったのを確かめてから、必須の制約をかける | contract |
Rails のマイグレーションなら、こう書けます(ActiveRecord 8.1 で確認)。
add_column :members, :plan, :string # 広げる
execute "UPDATE members SET plan = 'free' WHERE plan IS NULL" # 埋める
change_column_null :members, :plan, false # 締める
「プラン」のように全員分を決められる値なら、この順で最後まで進めます。
埋められない値もある
生まれ年はどうでしょう。生年月日を持っている会員なら、そこから埋められます。でも、持っていない会員の生まれ年は誰にも分かりません。
UPDATE 1 ← 生年月日のある1人だけ埋まった
ALTER TABLE ... SET NOT NULL
ERROR: column "birth_year" ... contains null values
この場合は「締める」に進めません。データベースでは null を許したまま、入力する画面の側で必須にする、という選択になります。
naminori の実例:登録のときだけ必須にした
naminori では2026年10月に、会員登録で生まれ年・お住まいの都道府県・いまの職種・興味・登録の目的・転職の希望時期を必ず聞くことにしました。そのとき決めたのが次のことです。
- データベースでは null を許す。既存の会員との整合を保つため
- 必須にするのは、登録フォームから会員を作るときだけ。招待・管理画面からの作成や、既存の会員の更新では求めない
- 生年月日を持っている既存の会員は、生まれ年を埋めておく
- 「既存の会員は、項目が空のままでも保存・更新できる」ことを、テストで確かめ続ける
既存の利用者に、必須を後付けしない
データベースではなく、プログラムの検証で必須にするときも同じ落とし穴があります。生まれ年を無条件に必須にすると、既存の会員が名前を変えようとしただけで保存に失敗します(手元で確認)。
無条件に必須 : 既存会員の更新 => false ["Birth year can't be blank"]
登録のときだけ必須: 既存会員の更新 => true
既存の利用者は、その項目が無い時代に登録した人です。新しい約束は、新しく入ってくるデータにだけかける。これが、機能追加のデータ設計の基本です。
このレクチャーで学ぶこと
- 必須の列をいきなり足すと失敗する理由
- 開発環境では通り、本番で失敗する落とし穴
- 広げる → 埋める → 締めるの3段階
- 埋められない値は、画面の側で必須にする(naminori の実例)
質問
まだ質問は投稿されていません。
質問投稿
ログイン後に質問の投稿ができます。