尝试向现有表添加NOT NULL列时出现以下错误。为什么会这样?我试过rake db:reset认为现有记录是问题,但即使重置数据库后,问题仍然存在。你能帮我解决这个问题。
迁移文件
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
答案 0 :(得分:152)
这是(我会考虑的)SQLite的一个小故障。无论表中是否有任何记录,都会发生此错误。
从头开始添加表时,可以指定NOT NULL,这是您使用“:null =&gt; 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数据库。
答案 1 :(得分:36)
表格中已有行,您正在添加新列division_id
。它需要每个现有行中的新列中的某些内容。
SQLite通常会选择NULL,但是你已经指定它不能为NULL,那么它应该是什么?它无从得知。
见:
该博客的建议是添加没有非null约束的列,并且每行都会添加NULL。然后,您可以在division_id
中填写值,然后使用change_column
添加非空约束。
请参阅我链接到的博客,了解执行此三个步骤的迁移脚本的说明。
答案 2 :(得分:6)
如果您有一个包含现有行的表,那么在添加null
约束之前,您需要更新现有行。 Guide on migrations建议使用本地模型,如下所示:
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
答案 3 :(得分:1)
以下迁移在Rails 6中为我工作:
class AddDivisionToProfile < ActiveRecord::Migration[6.0]
def change
add_reference :profiles, :division, foreign_key: true
change_column_null :profiles, :division_id, false
end
end
第一行注意:division
,第二行注意:division_id
答案 4 :(得分:1)
您可以添加具有默认值的列:
ALTER TABLE table1 ADD COLUMN userId INTEGER NOT NULL DEFAULT 1
答案 5 :(得分:0)
不要忘记,要求使用 ALTER TABLE ADD COLUMN NOT NULL 的默认值也有积极意义,至少在向具有现有数据的表中添加列时如此。如 https://www.sqlite.org/lang_altertable.html#altertabaddcol 中所述:
<块引用>ALTER TABLE 命令通过修改模式的 SQL 文本来工作 存储在 sqlite_schema 表中。没有对表进行任何更改 重命名或列添加的内容。正因为如此,执行 此类 ALTER TABLE 命令的时间与数据量无关 在表中。它们在具有 1000 万行的表上运行的速度与 在 1 行的表上。
文件格式本身支持此https://www.sqlite.org/fileformat.html
<块引用>一条记录的值可能少于列数 对应的表。例如,这可能发生在 ALTER 之后 TABLE ... ADD COLUMN SQL 语句增加了列数 在表模式中,而不修改表中预先存在的行。 记录末尾的缺失值使用 表中定义的相应列的默认值 模式。
使用这个技巧,可以通过仅更新架构来添加新列,操作耗时 387 毫秒,测试表有 670 万行。完全不触及数据区现有记录,节省大量时间。添加的列的缺失值来自架构,如果没有另外说明,默认值为 NULL。如果新列不是 NULL,则必须将默认值设置为其他值。
我不知道为什么表为空时ALTER TABLE ADD COLUMN NOT NULL没有特殊路径。一个好的解决方法可能是从一开始就创建表。