Обновление нескольких записей сразу в рельсах
В приложении rails 2, которое я создаю, мне нужно обновить коллекцию записей с определенными атрибутами. У меня есть именованная область, чтобы найти коллекцию, но я должен перебирать каждую запись для обновления атрибутов. Вместо того, чтобы сделать один запрос для обновления нескольких тысяч записей, мне придется сделать несколько тысяч запросов.
То, что я нашел до сих пор, похоже на Model.find_by_sql("UPDATE products ...)
Это чувствует себя действительно младше, но я googled и огляделся вокруг и не нашел ответа.
Для ясности у меня есть:
ps = Product.last_day_of_freshness
ps.each { |p| p.update_attributes(:stale => true) }
Я хочу:
Product.last_day_of_freshness.update_attributes(:stale => true)
Ответы
Ответ 1
Похоже, вы ищете ActiveRecord:: Base.update_all - из документации:
Обновляет все записи с подробными данными, если они соответствуют набору заданных условий, лимитам и порядку. Этот метод создает один оператор SQL UPDATE и отправляет его прямо в базу данных. Он не создает экземпляры задействованных моделей и не активирует обратные вызовы Active Record или проверки.
Product.last_day_of_freshness.update_all(:stale => true)
На самом деле, так как это rails 2.x(вы не указали) - цепочка named_scope может не работать, возможно, вам придется передать условия для вашей именованной области как второго параметра update_all, а не привязать ее к конец области продукта.
Ответ 2
Вы пытались использовать update_all
?
http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-update_all
Ответ 3
Лоос, такой как update_all, является лучшим вариантом... хотя я сохраню свою взломанную версию на случай, если вам будет интересно:
Вы можете использовать простой SQL-код, чтобы делать то, что вы хотите:
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)")
Обратите внимание, что это зависит от db - вам может потребоваться настроить стиль кавычек, чтобы он соответствовал.
Ответ 4
Для тех, кому необходимо обновить большое количество записей, миллион или даже больше, есть хороший способ обновления записей по партиям.
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
Если в вашей таблице много индексов, это также увеличит время для таких операций, потому что потребуется их перестроить. Когда я вызвал update_all
для всех записей в таблице, было около двух миллионов записей и двенадцать индексов, операция не была выполнена более чем за один час. При таком подходе на разработку потребовалось около 20 минут и около 4 минут на работу, конечно, это зависит от настроек приложения и серверного оборудования. Вы можете поместить его в rake task
или в некоторый фоновый рабочий.