我需要批量更新数千条记录,我想分批处理更新。首先,我试过了:
Foo.where(bar: 'bar').find_in_batches.update_all(bar: 'baz')
...我希望能生成如下的SQL:
"UPDATE foo SET bar = 'baz' where bar='bar' AND id > (whatever id is passed in by find_in_batches)"
这不起作用,因为find_in_batches返回一个数组,而update_all需要一个ActiveRecord关系。
这是我接下来尝试的:
Foo.where(bar: 'bar').select('id').find_in_batches do |foos|
ids = foos.map(&:id)
Foo.where(id: ids).update_all(bar: 'baz')
end
这样可行,但它显然会运行一个选择后跟更新,而不是基于我的'其中'条件。有没有办法清理它,以便选择和更新不必是单独的查询?
答案 0 :(得分:49)
在Rails 5中,有一个新的方便方法ActiveRecord::Relation#in_batches
来解决这个问题:
Foo.in_batches.update_all(bar: 'baz')
查看documentation了解详情。
答案 1 :(得分:10)
我也很惊讶,没有更简单的方法可以做到这一点......但我确实提出了这种方法:
batch_size = 1000
0.step(Foo.count, batch_size).each do |offset|
Foo.where(bar: 'bar').order(:id)
.offset(offset)
.limit(batch_size)
.update_all(bar: 'baz')
end
基本上这将:
0
和Foo.count
之间创建batch_size
和Foo.count == 10500
之间的偏移数组。例如,如果您[0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]
获得id
batch_size
排序,并限制为batch_size
。offset
的{{1}}条记录。这基本上是在生成的SQL中执行您所说的希望的手动方式。太糟糕了,它不能仅仅通过标准库方法以这种方式完成......虽然我确信你可以创建自己的方法。
答案 2 :(得分:5)
这是迟了2年,但这里的答案是:a)对于大型数据集来说非常慢; b)忽略内置轨道功能(http://api.rubyonrails.org/classes/ActiveRecord/Batches.html)。
当偏移值增加时,根据您的数据库服务器,它将执行序列扫描,直到它到达您的块,然后提取数据进行处理。当您的偏移量达到数百万时,这将非常慢。
使用“find_each”迭代器方法:
Foo.where(a: b).find_each do |bar|
bar.x = y
bar.save
end
这具有每次保存运行模型回调的额外好处。如果您不关心回调,请尝试:
Foo.where(a: b).find_in_batches do |array_of_foo|
ids = array_of_foo.collect &:id
Foo.where(id: ids).update_all(x: y)
end
答案 3 :(得分:3)
pdobb的答案是正确的,但在Rails 3.2.21中对我没有用,因为ActiveRecord没有用UPDATE调用解析OFFSET这个问题:
https://github.com/rails/rails/issues/10849
我相应地修改了代码,它在我的Postgres表上同时设置默认值时工作正常:
batch_size = 1000
0.step(Foo.count, batch_size).each do |offset|
Foo.where('id > ? AND id <= ?', offset, offset + batch_size).
order(:id).
update_all(foo: 'bar')
end
答案 4 :(得分:0)
答案 5 :(得分:0)
Haven还没有机会对此进行测试,但您可以使用ARel和子查询。
Foo.where(bar: 'bar').select('id').find_in_batches do |foos|
Foo.where( Foo.arel_table[ :id ].in( foos.to_arel ) ).update_all(bar: 'baz')
end