如何在SQLite3中解决"无法在默认值NULL中添加NOT NULL列"?

fel*_*lix 55 database sqlite ruby-on-rails sqlite3-ruby

尝试将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
Run Code Online (Sandbox Code Playgroud)

错误信息

SQLite3 :: SQLException:无法添加带有默认值NULL的NOT NULL列:ALTER TABLE"profiles"ADD"division_id"integer NOT NULL

Jai*_*yer 159

这是(我会考虑的)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
Run Code Online (Sandbox Code Playgroud)

我们在没有NOT NULL约束的情况下添加列,然后立即更改列以添加约束.我们可以这样做,因为虽然SQLite在列添加期间显然非常关注,但是对于列更改它并不那么挑剔.这是我书中清晰的设计气味.

它绝对是一个黑客,但它比多次迁移更短,它仍然适用于生产环境中更强大的SQL数据库.

  • 我很好奇这是如何工作的,因为SQLite中没有ALTER COLUMN. (15认同)
  • @BrianOrtiz,使用SQLite进行ALTER COLUMN的技术令人费解.您需要将表复制到临时表,删除现有表,使用修改后的列重新创建表,将临时表数据复制到新创建的表,最后删除临时表.这很可能是ROR所做的.而且,SQLite没有这种内置功能也是愚蠢的. (2认同)

Bil*_*win 39

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

SQLite通常会选择NULL,但是你已经指定它不能为NULL,那么应该是什么呢?它无从知晓.

看到:

该博客的建议是添加没有非null约束的列,并且每行都会添加NULL.然后您可以在中填入值division_id,然后使用change_column添加非空约束.

请参阅我链接到的博客,以获取执行此三个步骤的迁移脚本的说明.

  • 你对表格中已有行的假设听起来是正确的,而且几乎任何其他rdbms都是如此.但是,我在回答中指出SQLite是一个例外.即使表是空的,也会出现此错误,因此我发布了一个较短的解决方案. (12认同)
  • 这是不正确的 - sqlite 没有办法更改列。我不知道`change_column`是什么,但它不是sqlite。 (2认同)

Jos*_*phL 6

如果您有一个包含现有行的表,则需要在添加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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)


liv*_*ove 6

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

ALTER TABLE table1 ADD COLUMN userId INTEGER NOT NULL DEFAULT 1
Run Code Online (Sandbox Code Playgroud)