在Rails迁移中将一列更新为另一列的值


79

我在Rails应用程序中有一个表格,里面有成千上万条记录,而且它们只有一个created_at时间戳。我要添加编辑这些记录的功能,因此我想updated_at在表中添加时间戳。在添加列的迁移中,我想更新所有行以使其新的updated_at匹配旧的created_at,因为这是Rails中新创建的行的默认值。我可以做一个find(:all)并遍历记录,但是由于表的大小,要花几个小时。我真正想做的是:

UPDATE table_name SET updated_at = created_at;

在使用ActiveRecord而不是执行原始SQL的Rails迁移中,有没有更好的方法呢?

Answers:


133

我会创建一个迁移

rails g migration set_updated_at_values

并在其中编写如下内容:

class SetUpdatedAt < ActiveRecord::Migration
  def self.up
    Yourmodel.update_all("updated_at=created_at")
  end

  def self.down
  end
end

这样,您可以实现两件事

  • 这是一个可重复的过程,每执行一次可能的部署(在需要时)
  • 这是有效的。我想不出更有效的解决方案。

注意:如果查询变得很难使用activerecord编写,则还可以在迁移过程中运行原始sql。只需编写以下内容:

Yourmodel.connection.execute("update your_models set ... <complicated query> ...")

+1-最近,我必须完全做到这一点,并在ActiveRecord上使用SQL。它尽可能快。
彼得·布朗

40
Yourmodel.update_all 'update_at=created_at'更好,不是吗?它也适用于示波器。
马克-安德烈·Lafortune

根据Rails指南“如果执行up后跟一个,则数据库架构应保持不变down。因此,请def change仅考虑。
EliadL

1
@EliadL的几点评论:1)我们不是在更改架构,只是在更改数据库的内容。2)在编写此答案时,该change方法尚不存在,但是在这种情况下,我仍然更喜欢使用显式方法,up并且down更明确地使用(如果您想控制down应做的事情)。
nathanvda

20

您可以使用update_all,其工作方式与原始SQL非常相似。那就是您所有的选择。

顺便说一句,就我个人而言,我对迁移并不那么关注。有时原始SQL确实是最好的解决方案。通常,迁移代码不会重复使用。这是一次动作,因此我不必担心代码的纯度。


2
这取决于您的部署需求。我非常喜欢使用迁移,因为它们允许在现有平台上重复部署并获得相同的结果。我们有几个部署阶段:开发,测试,质量保证/接受,概念证明平台(用于测试客户),生产平台:我们需要能够将现有数据无故障地迁移到新部署的版本。在我们的案例中,添加列并确保数据可以正常进行不是一次动作。
nathanvda 2011年

我写过关于update_all在内部迁移文件中使用的方法:-)您也可以在内部迁移文件中执行原始SQL。但是update_all有点优雅。两者将执行完全相同的操作。
格雷格·丹

在迁移中声明模型通常是一个聪明的主意,因为如果稍后重新定义原始模型,这将防止出现问题。刚发现这篇文章它说明了一切相当不错:complicated-simplicity.com/2010/05/...
弗朗索瓦博索莱伊

随着update_all我不知道如何列的值设置为另一种,作为OP请求。请示范。
nathanvda 2011年

14

正如gregdan所写,您可以使用update_all。您可以执行以下操作:

Model.where(...).update_all('updated_at = created_at')

第一部分是您的典型条件集。最后一部分说明如何进行分配。UPDATE至少在Rails 4中,这将产生一个声明。


这在4.2中生成SET'posts'.'email' = 'options',选项是文字字符串
lulalala 2015年

确认此提示对我也不起作用。不确定这是完全错误的解决方案。不要回信@martin答案
woto 2015年

这是Rails控制台的输出:User.update_all('updated_at = created_at') SQL (0.4ms) UPDATE "users" SET updated_at = created_at
Martin Streicher 2015年

2

您可以直接运行以下命令 rails console ActiveRecord::Base.connection.execute("UPDATE TABLE_NAME SET COL2 = COL1")

例如:我想用项目表的remote_id更新项目表的sku。该命令将如下所示:
ActiveRecord::Base.connection.execute("UPDATE items SET sku = remote_id")


实际上,这是迄今为止最“历史”的安全方式,因为在将来(当某些运行迁移时,该模型Yourmodel可能已被删除。请避免在迁移中使用模型。)
Foton

0

这是一种通用的解决方法,无需编写查询,因为查询会面临风险。

  class Demo < ActiveRecord::Migration
    def change
     add_column :events, :time_zone, :string
     Test.all.each do |p|
       p.update_attributes(time_zone: p.check.last.time_zone)
     end
     remove_column :sessions, :time_zone
    end
  end

-4

作为一次操作,我只会在中进行操作rails console。真的需要几个小时吗?也许有数百万条记录…

records = ModelName.all; records do |r|; r.update_attributes(:updated_at => r.created_at); r.save!; end;`

从本质上讲,这是我首先尝试的方法,但是因为有成千上万的记录需要更改,所以需要花费数小时(几天?)。
jrdioko 2011年

当我对其进行测试时,它在我的开发人员机器(而不是服务器)上每秒大约记录50条记录。
jrdioko 2011年

4
如果可能,请始终避免迭代,避免使用“所有”一次将所有记录加载到RAM的“全部”功能,并且由于update_attributes已自动执行保存,因此需要进行额外的保存操作!将使整个操作花费两倍的时间。
ryan0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.