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

发布于 2024-09-07 17:38:07 字数 585 浏览 4 评论 0原文

尝试将 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”整数 NOT NULL

I am getting the following error while trying to add a NOT NULL column to an existing table. Why is it happening ?. I tried rake db:reset thinking that the existing records are the problem, but even after resetting the DB, the problem persists. Can you please help me figure this out.

Migration File

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

Error Message

SQLite3::SQLException: Cannot add a NOT NULL column with default value NULL: ALTER TABLE "profiles" ADD "division_id" integer NOT NULL

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

じее 2024-09-14 17:38:07

这是(我认为的)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 数据库配合使用。

This is (what I would consider) a glitch with SQLite. This error occurs whether there are any records in the table or not.

When adding a table from scratch, you can specify NOT NULL, which is what you're doing with the ":null => false" notation. However, you can't do this when adding a column. SQLite's specification says you have to have a default for this, which is a poor choice. Adding a default value is not an option because it defeats the purpose of having a NOT NULL foreign key - namely, data integrity.

Here's a way to get around this glitch, and you can do it all in the same migration. NOTE: this is for the case where you don't already have records in the database.

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

We're adding the column without the NOT NULL constraint, then immediately altering the column to add the constraint. We can do this because while SQLite is apparently very concerned during a column add, it's not so picky with column changes. This is a clear design smell in my book.

It's definitely a hack, but it's shorter than multiple migrations and it will still work with more robust SQL databases in your production environment.

蓝眼泪 2024-09-14 17:38:07

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

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

请参阅:

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

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

You already have rows in the table, and you're adding a new column division_id. It needs something in that new column in each of the existing rows.

SQLite would typically choose NULL, but you've specified it can't be NULL, so what should it be? It has no way of knowing.

See:

That blog's recommendation is to add the column without the not null constraint, and it'll be added with NULL in every row. Then you can fill in values in the division_id and then use change_column to add the not null constraint.

See the blogs I linked to for an description of a migration script that does this three-step process.

樱娆 2024-09-14 17:38:07

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

ALTER TABLE table1 ADD COLUMN userId INTEGER NOT NULL DEFAULT 1

You can add a column with a default value:

ALTER TABLE table1 ADD COLUMN userId INTEGER NOT NULL DEFAULT 1
叶落知秋 2024-09-14 17:38:07

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

轨道 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

If you have a table with existing rows then you will need to update the existing rows before adding your null constraint. The Guide on migrations recommends using a local model, like so:

Rails 4 and up:

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
桃气十足 2024-09-14 17:38:07

以下迁移在 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

change_column_null 的 API 文档

The following migration worked for me in 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

Note :division in the first line and :division_id in the second

API Doc for change_column_null

安静 2024-09-14 17:38:07

不要忘记,要求使用 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 语句增加了列数
在表架构中,而不修改表中预先存在的行。
记录末尾的缺失值使用
表中定义的相应列的默认值
架构。

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

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

Not to forget that there is also something positive in requiring the default value with ALTER TABLE ADD COLUMN NOT NULL, at least when adding a column into a table with existing data. As documented in https://www.sqlite.org/lang_altertable.html#altertabaddcol:

The ALTER TABLE command works by modifying the SQL text of the schema
stored in the sqlite_schema table. No changes are made to table
content for renames or column addition. Because of this, the execution
time of such ALTER TABLE commands is independent of the amount of data
in the table. They run as quickly on a table with 10 million rows as
on a table with 1 row.

The file format itself has support for this https://www.sqlite.org/fileformat.html

A record might have fewer values than the number of columns in the
corresponding table. This can happen, for example, after an ALTER
TABLE ... ADD COLUMN SQL statement has increased the number of columns
in the table schema without modifying preexisting rows in the table.
Missing values at the end of the record are filled in using the
default value for the corresponding columns defined in the table
schema.

With this trick it is possible to add a new column by updating just the schema, operation that took 387 milliseconds with a test table having 6.7 million rows. The existing records in the data area are not touched at all and the time saving is huge. The missing values for the added column come on-the-fly from the schema and the default value is NULL if not otherwise stated. If the new column is NOT NULL then the default value must be set to something else.

I do not know why there is not a special path for ALTER TABLE ADD COLUMN NOT NULL when the table is empty. A good workaround is perhaps to create the table right from the beginning.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文