web-dev-qa-db-fra.com

Mise à jour de plusieurs enregistrements à la fois dans des rails

Dans une application Rails 2 que je construis, j'ai besoin de mettre à jour une collection d'enregistrements avec des attributs spécifiques. J'ai une portée nommée pour trouver la collection, mais je dois parcourir chaque enregistrement pour mettre à jour les attributs. Au lieu de faire une requête pour mettre à jour plusieurs milliers d'enregistrements, je devrai faire plusieurs milliers de requêtes.

Ce que j’ai trouvé jusqu’à présent est quelque chose comme Model.find_by_sql("UPDATE products ...)

Cela semble vraiment junior, mais j'ai cherché dans Google et jeté un coup d'œil SO et je n'ai pas trouvé ma réponse.

Pour plus de clarté, ce que j'ai est:

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

Ce que je veux c'est:

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

On dirait que vous recherchez ActiveRecord :: Base.update_all - dans la documentation:

Met à jour tous les enregistrements avec les détails donnés s'ils correspondent à un ensemble de conditions fournies, des limites et un ordre peuvent également être fournis. Cette méthode construit une seule instruction SQL UPDATE et l'envoie directement à la base de données. Il n'instancie pas les modèles impliqués et ne déclenche pas de rappels ou de validations Active Record.

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

En fait, puisqu'il s'agit de Rails 2.x (vous n'avez pas spécifié) - l'enchaînement named_scope risque de ne pas fonctionner, vous devrez peut-être transmettre les conditions de votre étendue nommée en tant que second paramètre à update_all au lieu de l'enchaîner à la fin du processus. Portée du produit.

44
Brett Bender
5
lebreeze

Des boucles comme update_all sont la meilleure option ... bien que je maintienne ma version de hacky au cas où vous seriez curieux:

Vous pouvez utiliser tout simplement SQL pour faire ce que vous voulez ainsi:

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)")

Notez que cela dépend de la base de données. Vous devrez peut-être ajuster le style de citation en conséquence.

4
Taryn East

Pour ceux qui devront mettre à jour un grand nombre d'enregistrements, un million ou plus, il existe un bon moyen de mettre à jour des enregistrements par lots. 

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 votre table contient de nombreux index, cela augmentera également la durée de ces opérations, car il faudra les reconstruire. Dans mon cas, lorsque je n’exécutais que update_all pour tous les enregistrements de la table, il y avait environ deux millions et douze index, l’opération n’ayant pas abouti en plus d’une heure. Avec cette approche, le développement nécessitait environ 20 minutes et la production, environ 4 minutes. Bien sûr, cela devrait être en rake task ou en arrière-plan.

0
yozzz