NOT NULL列を既存のテーブルに追加しようとすると、次のエラーが表示されます。なぜそれが起こっているのですか?既存のレコードが問題だと思ってrake db:resetを試しましたが、DBをリセットした後でも問題は解決しません。これを理解するのを手伝ってもらえますか。
移行ファイル
class AddDivisionIdToProfile < ActiveRecord::Migration
def self.up
add_column :profiles, :division_id, :integer, :null => false
end
def self.down
remove_column :profiles, :division_id
end
end
エラーメッセージ
SQLite3 :: SQLException:デフォルト値NULLのNOT NULL列を追加できません:ALTER TABLE "profiles" ADD "division_id" integer NOT NULL
テーブルにすでに行があり、新しい列division_id
を追加しています。既存の各行の新しい列に何かが必要です。
SQLiteは通常NULLを選択しますが、NULLにできないことを指定したので、どうすればよいですか?知る方法がありません。
見る:
そのブログの推奨事項は、not null制約なしで列を追加することであり、すべての行にNULLを追加します。次に、division_id
に値を入力し、change_column
を使用して非null制約を追加します。
この3段階のプロセスを実行する移行スクリプトの説明については、私がリンクしたブログを参照してください。
これは(私が考えていることですが)SQLiteの不具合です。このエラーは、テーブルにレコードがあるかどうかに関係なく発生します。
テーブルを最初から追加する場合、NOT NULLを指定できます。これは、 ":null => false"表記で実行しています。ただし、列を追加するときにこれを行うことはできません。 SQLiteの仕様では、これにデフォルトを設定する必要がありますが、これは適切ではありません。デフォルト値の追加は、NOT NULL外部キーを持つ目的、つまりデータの整合性を無効にするため、オプションではありません。
この不具合を回避する方法を次に示します。すべて同じ移行で行うことができます。注:これは、データベースにまだレコードがない場合です。
class AddDivisionIdToProfile < ActiveRecord::Migration
def self.up
add_column :profiles, :division_id, :integer
change_column :profiles, :division_id, :integer, :null => false
end
def self.down
remove_column :profiles, :division_id
end
end
NOT NULL制約なしで列を追加してから、すぐに列を変更して制約を追加しています。これは、SQLiteが列の追加中に明らかに非常に心配している一方で、列の変更にそれほど気を配っていないためです。これは私の本では明確なデザインの匂いです。
これは間違いなくハックですが、複数の移行よりも短く、実稼働環境でより堅牢なSQLデータベースで動作します。
既存の行を持つテーブルがある場合、null
制約を追加する前に既存の行を更新する必要があります。 移行に関するガイド は、次のようなローカルモデルの使用を推奨しています。
Rails 4以降:
class AddDivisionIdToProfile < ActiveRecord::Migration
class Profile < ActiveRecord::Base
end
def change
add_column :profiles, :division_id, :integer
Profile.reset_column_information
reversible do |dir|
dir.up { Profile.update_all division_id: Division.first.id }
end
change_column :profiles, :division_id, :integer, :null => false
end
end
Rails 3
class AddDivisionIdToProfile < ActiveRecord::Migration
class Profile < ActiveRecord::Base
end
def change
add_column :profiles, :division_id, :integer
Profile.reset_column_information
Profile.all.each do |profile|
profile.update_attributes!(:division_id => Division.first.id)
end
change_column :profiles, :division_id, :integer, :null => false
end
end