ChatGPT解决这个技术问题 Extra ChatGPT

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

我在 Rails 应用程序中有一个包含数十万条记录的表,它们只有一个 created_at 时间戳。我正在添加编辑这些记录的功能,因此我想在表中添加一个 updated_at 时间戳。在添加列的迁移中,我想更新所有行以使新的 updated_at 与旧的 created_at 匹配,因为这是 Rails 中新创建的行的默认设置。我可以执行 find(:all) 并遍历记录,但由于表的大小,这需要几个小时。我真正想做的是:

UPDATE table_name SET updated_at = created_at;

在使用 ActiveRecord 而不是执行原始 SQL 的 Rails 迁移中,有没有更好的方法来做到这一点?


D
Deepak Mahakale

我会创建一个迁移

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

这样你就实现了两件事

这是一个可重复的过程,每次可能的部署(需要时)都会执行

这是有效的。我想不出一个更 rubyesque 的解决方案(同样有效)。

注意:如果查询太难使用 activerecord 编写,您也可以在迁移中运行原始 sql。只需编写以下内容:

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

+1 - 我最近不得不这样做,并在 ActiveRecord 上使用 SQL。它尽可能快。
Yourmodel.update_all 'update_at=created_at' 更好,不是吗?它也适用于范围。
根据 Rails guide“如果您执行 up 后跟 down”,则数据库架构应保持不变”。所以只考虑 def change
@EliadL 几点说明:1)我们没有更改架构,只是更改了数据库的内容。 2)在写这个答案时,change 方法还不存在,但在这种情况下,我仍然更喜欢使用显式 updown 来更明确(如果你想控制什么down 应该这样做)。
我认为你的 down 方法应该让事情回到 up 方法运行之前的状态,无论是数据库的结构还是内容。如果你不知道如何做到这一点,那么将 down 方法排除在外并使其无法回滚
G
Greg Dan

您可以使用与原始 SQL 非常相似的 update_all。这就是您拥有的所有选择。

顺便说一句,我个人不太关注迁移。有时原始 SQL 确实是最好的解决方案。通常不会重用迁移代码。这是一次性操作,所以我不关心代码纯度。


这取决于您的部署需求。我真的很喜欢使用迁移,因为它们允许在现有平台上重复部署并获得相同的结果。我们有几个部署阶段:开发、测试、质量保证/验收、概念验证平台(用于测试客户端)、生产平台:我们需要能够将现有数据迁移到新部署的版本而不会出现故障。在我们的例子中,添加一列并确保数据正常不是一次性操作。
我写了关于在迁移文件中使用 update_all 的文章 :-) 您还可以在迁移文件中执行原始 SQL。但是 update_all 更优雅一些。两者的表现完全相同。
在迁移中声明模型通常是一个聪明的主意,因为如果稍后重新定义原始模型,这将防止出现问题。刚刚发现这篇文章很好地解释了一切:complicated-simplicity.com/2010/05/…
对于 update_all,我不知道如何按照 OP 的要求将列的值设置为另一个列的值。请示范。
D
Deepak Mahakale

正如 gregdan 所写,您可以使用 update_all。你可以这样做:

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

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


这在 4.2 生成 SET'posts'.'email' = 'options',选项是文字字符串
确认这个提示对我也不起作用。不确定这是完全错误的解决方案。不要投票@martin 回答
这是 Rails 控制台的输出:User.update_all('updated_at = created_at') SQL (0.4ms) UPDATE "users" SET updated_at = created_at
S
Sarwan Kumar

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

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


实际上这是迄今为止最“历史”的安全方式,因为将来(当一些将运行迁移时,模型 Yourmodel 可能已经被删除。尽量避免在迁移中使用模型。
P
Pavel Dusanek

不要在迁移中使用应用程序模型,除非您在迁移中重新定义它们。如果您使用应用程序模型,您以后更改或删除迁移可能会失败。

当然,您也可以在迁移中使用 SQL 的全部功能。

阅读https://makandracards.com/makandra/15575-how-to-write-complex-migrations-in-rails


W
Wilson Varghese

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

  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

请解释:为什么查询会受到风险?如果您要将部署更改为不同的数据库?
M
Magdalena

您还可以添加 updated_at 列并在一次迁移中更新其值:

class AddUpdatedAtToTableName < ActiveRecord::Migration
  def change
    add_column :table_name, :updated_at, :datetime

    reversible do |dir|
      dir.up do
        update "UPDATE table_name SET updated_at=created_at"
      end
    end
  end
end

我认为这是一种不好的做法:我更喜欢将我的迁移分为 1) 架构更改和 2) 将数据更改分开。为了清楚起见。
D
Deepak Mahakale

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

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

这基本上是我首先尝试的,但是因为有数十万条记录需要更改,这将需要几个小时(几天?)。
当我测试它时,它在我的开发机器(不是服务器)上每秒大约有 50 条记录。
如果可能,请始终避免迭代,避免使用一次将每条记录加载到 RAM 中的“all”,并且由于 update_attributes 已经自动进行了保存,因此需要额外调用 save!将使整个操作花费两倍的时间。