Ruby on rails 无法在Sqlite3中添加默认值为NULL的NOT NULL列

Ruby on rails 无法在Sqlite3中添加默认值为NULL的NOT NULL列,ruby-on-rails,database,sqlite,sqlite3-ruby,Ruby On Rails,Database,Sqlite,Sqlite3 Ruby,我在尝试向现有表添加NOTNULL列时遇到以下错误。为什么会这样?。我尝试了rakedb:reset,认为现有记录是问题所在,但即使在重置db之后,问题仍然存在。你能帮我解决这个问题吗 迁移文件 class AddDivisionIdToProfilefalse 结束 def自动关闭 删除列:配置文件,:分区\u id 结束 结束 错误消息 SQLite3::SQLException:无法添加默认值为NULL的NOTNULL列:ALTER TABLE“profiles”add“division

我在尝试向现有表添加NOTNULL列时遇到以下错误。为什么会这样?。我尝试了rakedb:reset,认为现有记录是问题所在,但即使在重置db之后,问题仍然存在。你能帮我解决这个问题吗

迁移文件

class AddDivisionIdToProfilefalse
结束
def自动关闭
删除列:配置文件,:分区\u id
结束
结束
错误消息

SQLite3::SQLException:无法添加默认值为NULL的NOTNULL列:ALTER TABLE“profiles”add“division_id”integer NOT NULL


表中已经有行,正在添加一个新列
division\u id
。它需要在每个现有行的新列中都有一些内容

SQLite通常会选择NULL,但您已经指定它不能为NULL,那么它应该是什么呢?它没有办法知道

见:

  • (2009年,不再可用,因此这是存档网站上的快照)
  • (2014年)
该博客的建议是添加不带NOTNULL约束的列,并在每一行中添加null。然后,您可以在
division\u id
中填写值,然后使用
change\u列
添加NOTNULL约束

请参阅我链接的博客,了解执行此三步过程的迁移脚本的描述。

这是(我认为)SQLite的一个小故障。无论表中是否有任何记录,都会发生此错误

从头开始添加表时,可以指定NOTNULL,这就是使用“:NULL=>false”符号所做的。但是,在添加列时不能这样做。SQLite的规范说您必须为此设置一个默认值,这是一个糟糕的选择。添加默认值不是一个选项,因为它破坏了具有非空外键的目的,即数据完整性

这里有一种解决此问题的方法,您可以在同一个迁移中完成这一切。注意:这适用于数据库中没有记录的情况

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
class AddDivisionIdToProfilefalse
结束
def自动关闭
删除列:配置文件,:分区\u id
结束
结束
我们在不使用NOTNULL约束的情况下添加列,然后立即更改列以添加约束。我们可以这样做,因为虽然SQLite在列添加过程中显然非常关心,但它对列更改并不那么挑剔。在我的书中,这是一种清晰的设计风格


这无疑是一个黑客攻击,但它比多次迁移要短,并且在您的生产环境中仍然可以使用更健壮的SQL数据库。

如果您有一个包含现有行的表,那么您需要在添加
null
约束之前更新现有行。建议使用本地模型,如下所示:

轨道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
class AddDivisionIdToProfilefalse
结束
结束
轨道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
class AddDivisionIdToProfiledivision.first.id)
结束
更改列:profiles,:division\u id,:integer,:null=>false
结束
结束

以下迁移对我在Rails 6中起到了作用:

class AddDivisionToProfile
注意第一行中的
:division
,第二行中的
:division\u id


您可以添加具有默认值的列:

ALTER TABLE table1 ADD COLUMN userId INTEGER NOT NULL DEFAULT 1

不要忘记,要求ALTERTABLEADDCOLUMNNOTNULL的默认值也有积极意义,至少在将列添加到包含现有数据的表中时是如此。如以下文件所述:

ALTERTABLE命令通过修改模式的SQL文本来工作 存储在sqlite_模式表中。未对表进行任何更改 用于重命名或添加列的内容。因此,执行 此类ALTER TABLE命令的时间与数据量无关 在桌子上。它们在1000万行的表上运行的速度与 在一个有一行的桌子上

文件格式本身支持这一点

一条记录的值可能少于记录中的列数 对应的表格。这可能发生,例如,在改变之后 桌子添加列SQL语句增加了列数 在表架构中,而不修改表中先前存在的行。 使用 表中定义的相应列的默认值 模式

有了这个技巧,就可以通过只更新模式来添加一个新列,这个操作需要387毫秒,测试表有670万行。数据区中的现有记录完全不被触及,节省了大量时间。添加列的缺失值来自架构,如果没有另外说明,默认值为NULL。如果新列不为NULL,则必须将默认值设置为其他值

我不知道为什么会这样