如果我想更新包含各种不同值的300,000条记录的列,我该如何使用update_all
?
我想做的是:
Model.update_all(:column => [2,33,94,32]).where(:id => [22974,22975,22976,22977])
但不幸的是,这不起作用,对于300,000个条目来说更糟糕。
答案 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年的答案:
最受欢迎的答案是错误的;如作者本人所言,它将触发n
SQL查询,每一行一次。
第二个最受好评的答案是“ 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.