Подтвердить что ты не робот

Обновление нескольких записей сразу в рельсах

В приложении 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)
4b9b3361

Ответ 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, а не привязать ее к конец области продукта.

Ответ 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 или в некоторый фоновый рабочий.