如何在 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) 迁移无法知道列在潜在还原中的更改内容。所以对于这种情况,我会坚持使用up
和down
。
您不应该使用模态本身在迁移中进行数据更改。例如,您可能会在以后的迁移中删除模型(本例中为 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 迁移中将可空列更改为不可空?的主要内容,如果未能解决你的问题,请参考以下文章
Entity Framework Core:将可空列迁移到必需时的默认值