18

update_all如果我想用各种不同的值更新包含 300,000 条记录的列,我该如何使用?

我想做的是:

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

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

4

4 回答 4

15

ActiveRecord#update 文档中:

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 查询......所以可能不是最好的解决方案

于 2013-08-13T07:13:49.590 回答
9

我发现这样做的唯一方法是生成带有更新值的 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 |
+--------+--------------+---------+------------+-----+-------------------------+-------------------------+
于 2016-02-05T12:09:07.490 回答
5

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

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

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

于 2012-01-07T17:19:00.123 回答
4

这是我 2020 年的回答:

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

  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也是可选的,但很高兴知道它在那里。

于 2020-09-18T21:36:10.837 回答