当所有记录不同时,如何使用全部更新?

时间:2012-01-07 11:36:02

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

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

我想做的是:

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

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

5 个答案:

答案 0 :(得分:14)

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

答案 1 :(得分:4)

我发现这样做的唯一方法是使用更新的值生成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.url = i}

在单个请求中保存记录

CustomProduct.import a.to_a, :on_duplicate_key_update => [:url]

所有记录都将在单个请求中更新。有关详细信息,请参阅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 |
+--------+--------------+---------+------------+-----+-------------------------+-------------------------+

答案 2 :(得分:3)

对你的问题的简短回答是,你做不到。

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

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

答案 3 :(得分:2)

这是我2020年的答案:

  1. 最受欢迎的答案是错误的;如作者本人所言,它将触发n SQL查询,每一行一次。

  2. 第二个最受好评的答案是“ activerecord-import”,这是可以走的路。但是,它是通过实例化ActiveRecord模型来实现的,并且如果您正在从事此类宝石业务,那么您可能正在寻找极佳的性能(无论如何我们都是这种情况)。

这就是我们所做的。首先,您构建一个哈希数组,每个哈希包含要更新的记录的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也是可选的,但很高兴知道它在那里。

答案 4 :(得分:0)

我发现使用单个SQL查询且没有任何多余的gem做到这一点的最佳方法是遍历新值,并以单个字符串的形式在原始SQL中创建更新,然后执行该更新。运作方式如下:

updates = [{id: 1, column: 4}, {id: 2, column: 8}]
update_sql = ''
updates.each do |update|
  update_sql += "UPDATE model SET column = #{update[:column]} WHERE id = #{update[:id]};"
end

ActiveRecord::Base.connection.execute(update_sql)


Just be careful about possible SQL Injection attacks here, since nothing is escaped.