【问题标题】:How to use update all, when all records are different?当所有记录都不同时,如何使用全部更新?
【发布时间】:2012-02-04 20:42:29
【问题描述】:

如果我想更新包含 300,000 条记录的列,所有这些记录都具有各种不同的值,我该如何使用 update_all

我想做的是这样的:

Model.update_all(:column => [2,33,94,32]).where(:id => [22974,22975,22976,22977]) 

但不幸的是,这不起作用,对于 300,000 个条目来说更糟。

【问题讨论】:

  • 我不明白这个问题。 Model.update_all(:attribute => value) 在 3 和 300000 条记录上的工作方式相同。
  • 我想要的是这样的:Model.update_all(:column => [2,33,94,32]).where(:id => [22974,22975,22976,22977])

标签: ruby-on-rails-3 activerecord bulk update-all


【解决方案1】:

来自ActiveRecord#update documentation

people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)

所以在你的情况下:

updates = {22974 => {column: 2}, 22975 => {column: 33}, 22976 => {column: 94}, 22977 => {column: 32}}
Model.update(updates.keys, updates.values)

编辑:刚刚查看了源代码,这也在生成 n SQL 查询......所以可能不是最好的解决方案

【讨论】:

    【解决方案2】:

    我发现这样做的唯一方法是生成带有更新值的 INSERT INTO 请求。我为此使用gem "activerecord-import"

    例如, 我有一个带有 val 值的表格

    +--------+--------------+---------+------------+-----+-------------------------+-------------------------+
    | pkey   | id           | site_id | feature_id | val | created_at              | updated_at              |
    +--------+--------------+---------+------------+-----+-------------------------+-------------------------+
    | 1      |              | 125     | 7          | 88  | 2016-01-27 10:25:45 UTC | 2016-02-05 11:18:14 UTC |
    | 111765 | 0001-0000024 | 125     | 7          | 86  | 2016-01-27 11:33:22 UTC | 2016-02-05 11:18:14 UTC |
    | 111766 | 0001-0000062 | 125     | 7          | 15  | 2016-01-27 11:33:22 UTC | 2016-02-05 11:18:14 UTC |
    | 111767 | 0001-0000079 | 125     | 7          | 19  | 2016-01-27 11:33:22 UTC | 2016-02-05 11:18:14 UTC |
    | 111768 | 0001-0000086 | 125     | 7          | 33  | 2016-01-27 11:33:22 UTC | 2016-02-05 11:18:14 UTC |
    +--------+--------------+---------+------------+-----+-------------------------+-------------------------+
    

    选择记录

    products = CustomProduct.limit(5)
    

    根据需要更新记录

    products.each_with_index{|p, i| p.val = i}
    

    在单个请求中保存记录

    CustomProduct.import products.to_a, :on_duplicate_key_update => [:val]
    

    您的所有记录将在单个请求中更新。请查看gem "activerecord-import" 文档了解更多详情。

    +--------+--------------+---------+------------+-----+-------------------------+-------------------------+
    | pkey   | id           | site_id | feature_id | val | created_at              | updated_at              |
    +--------+--------------+---------+------------+-----+-------------------------+-------------------------+
    | 1      |              | 125     | 7          | 0   | 2016-01-27 10:25:45 UTC | 2016-02-05 11:19:49 UTC |
    | 111765 | 0001-0000024 | 125     | 7          | 1   | 2016-01-27 11:33:22 UTC | 2016-02-05 11:19:49 UTC |
    | 111766 | 0001-0000062 | 125     | 7          | 2   | 2016-01-27 11:33:22 UTC | 2016-02-05 11:19:49 UTC |
    | 111767 | 0001-0000079 | 125     | 7          | 3   | 2016-01-27 11:33:22 UTC | 2016-02-05 11:19:49 UTC |
    | 111768 | 0001-0000086 | 125     | 7          | 4   | 2016-01-27 11:33:22 UTC | 2016-02-05 11:19:49 UTC |
    +--------+--------------+---------+------------+-----+-------------------------+-------------------------+
    

    【讨论】:

    • 注意数据库适配器必须实现sql_for_on_duplicate_key_update,否则你会得到类似NoMethodError: undefined method sql_for_on_duplicate_key_update' for #<ActiveRecord::ConnectionAdapters::PostGISAdapter:0x0055a28cec6f10>的错误。
    • @Pete 谢谢你的评论!
    • 请注意 :on_duplicate_key_update 仅适用于 MySQL 和 PostgreSQL 9.5+ (github.com/zdennis/activerecord-import/wiki/…)
    • 请注意,重复键更新会增加表上的自动增量计数器(如果有的话)。更多信息在这里stackoverflow.com/a/23517191/2560641
    【解决方案3】:

    您的问题的简短回答是,您不能。

    update_all 的目的是为 all 记录的列分配 same 值(如果提供,则匹配条件)。有用的原因是它在单个 SQL 语句中完成。

    我同意 Shime 的正确答案。虽然这会产生 n 个 SQL 调用。所以,也许你的问题还有更多你没有告诉我们的东西。也许您可以遍历每个可能的值,为应该使用该值更新的对象调用 update_all。然后是构建适当的哈希,或者更好的是,如果条件基于模型本身的某些内容,您可以将条件传递给 update_all。

    【讨论】:

      【解决方案4】:

      这是我 2020 年的答案:

      1. 投票最多的答案是错误的;正如作者自己所说,它将触发n SQL 查询,每行一个。

      2. 第二个最受好评的答案建议 gem “activerecord-import”,这是要走的路。但是,它是通过实例化 ActiveRecord 模型来实现的,如果您正在为这样的 gem 做生意,那么您可能正在寻找极致的性能(无论如何都是我们的情况)。

      所以这就是我们所做的。首先,您构建一个哈希数组,每个哈希包含您要更新的记录的id 和任何其他字段。

      例如:

      records = [{ id: 1, name: 'Bob' }, { id: 2, name: 'Wilson' },...]

      然后你像这样调用 gem:

      YourModelName.import(records, on_duplicate_key_update: [:name, :other_columns_whose_keys_are_present_in_the_hash], validate: false, timestamps: false)
      

      解释:

      • on_duplicate_key_update 意味着,如果数据库在主键上发现冲突(并且它会在每一行上,因为我们正在讨论更新现有记录),它不会失败,而是更新您传递的列那个数组。

      • 如果您不validate false(默认为true),它将尝试为每一行实例化一个新模型实例,并且可能由于验证而失败(因为您的哈希仅包含部分信息)。

      • timestamp false 也是可选的,但很高兴知道它在那里。

      【讨论】:

      • 由于某种原因,它不会为我更新现有记录,而是尝试创建新记录
      • 很好的答案,但是如果需要时间戳列会很烦人,因此插入语句失败:facepalm:
      猜你喜欢
      • 1970-01-01
      • 2013-03-08
      • 1970-01-01
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多