何千ものレコードを大量に更新する必要があり、バッチで更新を処理したいと思います。まず、私が試した:
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
それは機能しますが、明らかに、「場所」条件に基づく単一の更新ではなく、選択に続いて更新が実行されます。選択と更新を別々のクエリにする必要がないように、これをクリーンアップする方法はありますか?
Rails 5では、新しい便利なメソッドActiveRecord::Relation#in_batches
この問題を解決するために:
Foo.in_batches.update_all(bar: 'baz')
詳細については documentation を確認してください。
私もこれを行う簡単な方法がないことに驚いています...しかし、私はこのアプローチを思いつきました:
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
に限定してください。offset
より大きい最大batch_size
レコードを更新します。これは基本的に、生成されたSQLで望んでいたと言ったことを手動で実行する方法です。残念なことに、標準のライブラリメソッドでは、この方法で既にこれを行うことはできません。ただし、独自のライブラリメソッドを作成できると確信しています。
これは2年遅れですが、ここでの答えは、a)大規模なデータセットでは非常に遅く、b)組み込みのRails機能( http://api.rubyonrails.org /classes/ActiveRecord/Batches.html )。
オフセット値が増加すると、DBサーバーに応じて、ブロックに到達するまでシーケンススキャンが実行され、処理のためにデータがフェッチされます。オフセットが数百万に達すると、これは非常に遅くなります。
「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
pdobbの答えは正しい軌道に乗っていますが、Rails 3.2.21ではActiveRecordがUPSET呼び出しで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
Update_allをバッチで呼び出す小さなメソッドを作成しました。
https://Gist.github.com/VarunNatraaj/420c638d544be59eef85
それが役に立つことを願っています! :)
これをテストする機会はまだありませんが、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