web-dev-qa-db-ja.com

SQLite3で「デフォルト値がNULLのNOT NULL列を追加できません」を解決するにはどうすればよいですか?

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

55
felix

テーブルにすでに行があり、新しい列division_idを追加しています。既存の各行の新しい列に何かが必要です。

SQLiteは通常NULLを選択しますが、NULLにできないことを指定したので、どうすればよいですか?知る方法がありません。

見る:

そのブログの推奨事項は、not null制約なしで列を追加することであり、すべての行にNULLを追加します。次に、division_idに値を入力し、change_columnを使用して非null制約を追加します。

この3段階のプロセスを実行する移行スクリプトの説明については、私がリンクしたブログを参照してください。

39
Bill Karwin

これは(私が考えていることですが)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データベースで動作します。

154
Jaime Bellmyer

既存の行を持つテーブルがある場合、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
6
JosephL