66

尝试将 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

4

6 回答 6

173

这是(我认为)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 在列添加期间显然非常关心,但它对列更改并不那么挑剔。这是我书中明显的设计气味。

这绝对是一个 hack,但它比多次迁移更短,并且它仍然可以在您的生产环境中使用更强大的 SQL 数据库。

于 2011-07-15T16:34:52.963 回答
49

您已经在表中有行,并且您正在添加一个新列division_id。它需要每个现有行的新列中的某些内容。

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

看:

该博客的建议是添加没有非空约束的列,并且它将在每一行中添加 NULL。然后您可以在 中填写值,division_id然后使用change_column添加非空约束。

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

于 2010-07-03T07:33:41.693 回答
6

如果您有一个包含现有行的表,那么您需要在添加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

导轨 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
于 2013-10-07T04:11:19.810 回答
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第二行

API 文档change_column_null

于 2020-05-03T15:05:54.790 回答
0

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

ALTER TABLE table1 ADD COLUMN userId INTEGER NOT NULL DEFAULT 1
于 2021-02-17T03:14:04.727 回答
0

不要忘记,要求使用 ALTER TABLE ADD COLUMN NOT NULL 的默认值也有好处,至少在将列添加到包含现有数据的表中时是这样。如https://www.sqlite.org/lang_altertable.html#altertabaddcol中所述:

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

文件格式本身支持此https://www.sqlite.org/fileformat.html

一条记录的值可能少于对应表中的列数。例如,在 ALTER TABLE ... ADD COLUMN SQL 语句增加了表模式中的列数而不修改表中预先存在的行之后,就会发生这种情况。使用表模式中定义的相应列的默认值填充记录末尾的缺失值。

使用这个技巧,可以通过仅更新模式来添加新列,该操作需要 387 毫秒,测试表有 670 万行。数据区中已有的记录根本不被触及,节省的时间是巨大的。添加的列的缺失值来自模式,如果没有另外说明,默认值为 NULL。如果新列不为空,则必须将默认值设置为其他值。

不知道为什么表为空时没有ALTER TABLE ADD COLUMN NOT NULL 的特殊路径。一个好的解决方法可能是从一开始就创建表。

于 2021-04-26T14:21:16.427 回答