2011-03-22 19 views
23

En una aplicación Rails 2 que estoy compilando, tengo la necesidad de actualizar una colección de registros con atributos específicos. Tengo un alcance con nombre para encontrar la colección, pero tengo que iterar sobre cada registro para actualizar los atributos. En lugar de hacer una consulta para actualizar varios miles de registros, tendré que hacer varias miles de consultas.Actualizando varios registros a la vez en rieles

Lo que he encontrado hasta ahora es algo así como Model.find_by_sql("UPDATE products ...)

Esto se siente muy joven, pero yo Googled y miraron a su alrededor SO y no he encontrado mi respuesta.

Para mayor claridad, lo que tengo es:

ps = Product.last_day_of_freshness 
ps.each { |p| p.update_attributes(:stale => true) } 

Lo que quiero es:

Product.last_day_of_freshness.update_attributes(:stale => true) 

Respuesta

40

Suena como que busca ActiveRecord::Base.update_all - a partir de la documentación:

Actualiza todos los registros con detalles proporcionados si coinciden con un conjunto de condiciones suministradas, también se pueden suministrar límites y el orden. Este método construye una sola instrucción SQL UPDATE y la envía directamente a la base de datos. No crea instancias de los modelos involucrados y no desencadena devoluciones de llamada o validaciones de Active Record.

Product.last_day_of_freshness.update_all(:stale => true) 

En realidad, ya que este es raíles 2.x (No ha especificado) - el encadenamiento named_scope puede no funcionar, es posible que tenga que pasar las condiciones para su ámbito con nombre como segundo parámetro a update_all en lugar de encadenarlo al final del alcance del Producto.

+1

update_all solo está disponible en Rails 3 - desafortunadamente la pregunta sobre Rails 2. –

+2

@Taryn East no es verdad, 'update_all' se movió de ActiveRecord :: Base a ActiveRecord :: Relación entre rails 2.3.xy 3.0 - I ha actualizado el enlace y ha agregado un comentario para aclarar. –

+0

Ah genial, gracias ...¡no me di cuenta de eso! –

3

Loos como update_all es la mejor opción ... aunque voy a mantener mi versión hacky en caso de que usted es curioso:

Se puede utilizar solo SQL-ole llano para hacer lo que quiere así:

ps = Product.last_day_of_freshness 
ps_ids = ps.map(%:id).join(',') # local var just for readability 
Product.connection.execute("UPDATE `products` SET `stale` = TRUE WHERE id in (#{ps_ids)") 

Tenga en cuenta que esto es dependiente dB - es posible que tenga que ajustar citando estilo para adaptarse.

0

Para aquellos que necesitarán actualizar una gran cantidad de registros, un millón o incluso más, existe una buena manera de actualizar los registros por lotes.

product_ids = Product.last_day_of_freshness.pluck(:id) 
iterations_size = product_ids.count/5000 

puts "Products to update #{product_ids.count}" 

product_ids.each_slice(5000).with_index do |batch_ids, i| 
    puts "step #{i} of iterations_size" 
    Product.where(id: batch_ids).update_all(stale: true) 
end 

Si la tabla tiene un bajo número de índices, sino que también aumentará el tiempo para este tipo de operaciones, ya que será necesario volver a crearlas. En mi caso, cuando ejecuté solo update_all para todos los registros en la tabla, hubo aproximadamente dos millones, y doce índices, la operación no se realizó en más de una hora. Con este enfoque, tomó aproximadamente 20 minutos en desarrollo env y aproximadamente 4 minutos en producción. Por supuesto, esto debería estar en rake task o algún trabajador de segundo plano.

Cuestiones relacionadas