如何在 Rails 迁移中将可空列更改为不可空?

Posted

技术标签:

【中文标题】如何在 Rails 迁移中将可空列更改为不可空?【英文标题】:How to change a nullable column to not nullable in a Rails migration? 【发布时间】:2011-08-23 10:28:12 【问题描述】:

我在之前的迁移中创建了一个日期列并将其设置为可为空。现在我想将其更改为不可为空。假设该数据库中有空行,我该怎么做?如果这些列当前为空,我可以将这些列设置为 Time.now。

【问题讨论】:

【参考方案1】:

创建具有change_column 语句和:default => 值的迁移。

change_column :my_table, :my_column, :integer, :default => 0, :null => false

见:change_column

根据数据库引擎,您可能需要使用change_column_null

【讨论】:

这对我有用。在本地使用 mysql。当在 Heroku (Postgres) 中推送和运行应用程序时,当我将它写为 null 时,它会在不为 null 的列上出错——这是正确的。只有“change_column_null”才能工作,不能在 MySql 上使用“change_column ... :null => false”。谢谢。 那么在 change_column_null 之后您的迁移是什么 Postges 比 MySQL 更严格——我希望它需要 change_column_null @rtfminc 我强烈建议您在开发和生产中使用相同的数据库引擎,因为它可以避免很多边缘情况的问题。【参考方案2】:

如果你在迁移中这样做,那么你可能会这样做:

# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)

# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false

【讨论】:

请注意,因为这让我破坏了我的开发数据库。而是使用显式哈希语法,例如:MyModel.update_all(:date_column => Time.now, :date_column => nil)。您原始表单中的查询刚刚使我所有的模型在该字段中都具有 nil 值。 这次迁移你有使用'up'/'down'的方法,还是迁移中的简单更改方法? change 方法不太适合这种情况,因为 (1) update_all 方法将在迁移和潜在还原时执行。这可能不是最糟糕的事情,但因为 (2) 迁移无法知道列在潜在还原中的更改内容。所以对于这种情况,我会坚持使用updown 您不应该使用模态本身在迁移中进行数据更改。例如,您可能会在以后的迁移中删除模型(本例中为 MyModel)。如果有人然后运行您的所有迁移,它将中断。而是直接使用 ActiveRecord::Base.connection.execute('your sql here') 执行 SQL 语句 对于任何感兴趣的人,my answer 展示了如何一步完成。【参考方案3】:

在 Rails 4 中,这是一个更好的(DRYer)解决方案:

change_column_null :my_models, :date_column, false

为确保该列中不存在具有NULL 值的记录,您可以传递第四个参数,这是用于具有NULL 值的记录的默认值:

change_column_null :my_models, :date_column, false, Time.now

【讨论】:

当表已经有空值时,这会导致问题。 See my answer 在 3.2 中也可用。也有第 4 个参数,用于设置默认值为 null。 change_column_null 加 1。然而,Rick Smith 的上述评论指出了一个非常有效的案例。 更新为添加更新空值的查询。第 4 个参数(默认值)仅在您确实希望为将来的记录设置默认值时才有用。 实际上,根据 Rails 4.2 文档,第四个参数不会为将来的记录设置默认值:“该方法接受可选的第四个参数来替换现有的 +NULL+s 与其他值。请注意第四个参数不设置列的默认值。"【参考方案4】:

导轨 4:

def change
  change_column_null(:users, :admin, false )
end

【讨论】:

请描述您的答案。【参考方案5】:

Rails 4(其他 Rails 4 答案有问题):

def change
  change_column_null(:users, :admin, false, <put a default value here> )
  # change_column(:users, :admin, :string, :default => "")
end

将其中包含 NULL 值的列更改为不允许 NULL 会导致问题。这正是可以在您的开发设置中正常工作的代码类型,然后在您尝试将其部署到 LIVE 生产环境时崩溃。您应该首先将 NULL 值更改为有效的值,然后 然后 不允许 NULL。 change_column_null 中的第 4 个值正是这样做的。详情请见documentation。

另外,我通常更喜欢为字段设置默认值,这样我就不需要在每次创建新对象时都指定字段的值。我也包含了注释掉的代码来执行此操作。

【讨论】:

对于 Rails 4,这似乎是最准确和最完整的答案,包括注释掉的默认设置。 如果您正在向表中添加新列并希望为 null 插入新值,但不想为该列添加默认值,您可以在迁移中执行此操作:@ 987654324@ 然后change_column_null(:admin, :string, false, "new_value_for_existing_records")【参考方案6】:

Rails 4.02+ 中,根据 docs 没有像 update_all 这样的方法,带有 2 个参数。相反,可以使用以下代码:

# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)

# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false

【讨论】:

【参考方案7】:

如果您有现有记录,则不能使用 add_timestamps 和 null:false,因此这是解决方案:

def change
  add_timestamps(:buttons, null: true)

  Button.find_each  |b| b.update(created_at: Time.zone.now, updated_at: Time.zone.now) 

  change_column_null(:buttons, :created_at, false)
  change_column_null(:buttons, :updated_at, false)
end

【讨论】:

【参考方案8】:

根据Strong Migrations gem,在生产中使用change_column_null 是个坏主意,因为它会在检查所有记录时阻止读写。

处理这些迁移(特定于 Postgres)的推荐方法是将此过程分成两个迁移。

一个用约束改变表:

class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
    end
  end
end

还有一个单独的迁移来验证它:

class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
    end
  end
end

以上示例是从链接文档中提取(并稍作更改)的。显然对于 Postgres 12+,您还可以将 NOT NULL 添加到架构中,然后在运行验证后删除约束:

class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
    end

    # in Postgres 12+, you can then safely set NOT NULL on the column
    change_column_null :users, :some_column, false
    safety_assured do
      execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
    end
  end
end

当然,这意味着对于 Postgres 的早期版本,您的架构不会显示该列是 NOT NULL,因此我还建议设置模型级别验证以要求该值存在(尽管我建议即使对于允许此步骤的 PG 版本也是如此)。

此外,在运行这些迁移之前,您需要使用 null 以外的值更新所有现有记录,并确保写入表的任何生产代码都没有为值写入 null

【讨论】:

以上是关于如何在 Rails 迁移中将可空列更改为不可空?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 oracle 中使可空列不为空

Entity Framework Core:将可空列迁移到必需时的默认值

如何在 PostgreSQL 11.1 中将现有列更改为身份

如何在sql查询中将列更改为行

如何在 PGSQL 中将布尔列更改为位变化

如何在 MySQL 中将 mediumtext 列更改为 varchar? (换表)