seleccionadas regla que puedan para instancias instancia hay existen cumple compartidas como caracteristicas caracteristica actualizarlas actualizar actualizacion sql ruby-on-rails

sql - regla - ¿Cómo puedo ejecutar actualizaciones en lotes en Rails 3/4?



no hay caracteristicas seleccionadas para actualizarlas sql (6)

Aún no he tenido la oportunidad de probar esto, pero es posible que puedas usar ARel y una consulta secundaria.

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

Necesito actualizar en masa muchos miles de registros, y me gustaría procesar las actualizaciones en lotes. Primero, intenté:

Foo.where(bar: ''bar'').find_in_batches.update_all(bar: ''baz'')

... que esperaba que generara SQL como:

"UPDATE foo SET bar = ''baz'' where bar=''bar'' AND id > (whatever id is passed in by find_in_batches)"

Eso no funciona porque find_in_batches devuelve una matriz, mientras que update_all necesita una relación ActiveRecord.

Esto es lo que intenté a continuación:

Foo.where(bar: ''bar'').select(''id'').find_in_batches do |foos| ids = foos.map(&:id) Foo.where(id: ids).update_all(bar: ''baz'') end

Eso funciona, pero obviamente ejecuta una selección seguida por la actualización, en lugar de una única actualización basada en mis condiciones ''donde''. ¿Hay alguna forma de limpiar esto, para que la selección y la actualización no tengan que ser consultas separadas?


En Rails 5, hay un nuevo método práctico ActiveRecord::Relation#in_batches para resolver este problema:

Foo.in_batches.update_all(bar: ''baz'')

Consulte la documentation para más detalles.


Esto es 2 años tarde, pero las respuestas aquí son a) muy lentas para grandes conjuntos de datos yb) ignoran las capacidades de los rieles integrados ( http://api.rubyonrails.org/classes/ActiveRecord/Batches.html ).

A medida que aumenta el valor de compensación, dependiendo de su servidor de base de datos, realizará una exploración de secuencia hasta que llegue a su bloque, y luego buscará los datos para procesarlos. A medida que su compensación llegue a millones, esto será extremadamente lento.

use el método iterador "find_each":

Foo.where(a: b).find_each do |bar| bar.x = y bar.save end

Esto tiene la ventaja adicional de ejecutar las devoluciones de llamada del modelo con cada guardado. Si no le interesan las devoluciones de llamada, intente:

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



La respuesta de pdobb está en el camino correcto, pero no me funcionó en Rails 3.2.21 debido a este problema de ActiveRecord que no analiza OFFSET con las llamadas UPDATE:

github.com/rails/rails/issues/10849

Modifiqué el código en consecuencia y funcionó bien para establecer simultáneamente el valor predeterminado en mi tabla de 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


También me sorprende que no haya una forma más fácil de hacer esto ... pero se me ocurrió este enfoque:

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

Básicamente esto será:

  1. Cree una matriz de compensaciones entre 0 y Foo.count paso a paso por batch_size cada vez. Por ejemplo, si Foo.count == 10500 obtendrías: [0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]
  2. Recorra estos números y utilícelos como DESPLAZAMIENTO en la consulta SQL, asegurándose de ordenar por id y limitando el batch_size del batch_size .
  3. Actualice en la mayoría de los registros batch_size cuyo "índice" sea mayor que el offset .

Esta es básicamente la forma manual de realizar lo que dijo que esperaba en el SQL generado. Lástima que no se pueda hacer de esta manera con un método de biblioteca estándar ... aunque estoy seguro de que podría crear uno propio.