Обновление сразу нескольких записей в рельсах - PullRequest
26 голосов
/ 22 марта 2011

В приложении rails 2, которое я создаю, мне нужно обновить коллекцию записей с определенными атрибутами.У меня есть именованная область, чтобы найти коллекцию, но я должен перебирать каждую запись, чтобы обновить атрибуты.Вместо того, чтобы сделать один запрос для обновления нескольких тысяч записей, мне придется сделать несколько тысяч запросов.

То, что я нашел до сих пор, выглядит примерно так: Model.find_by_sql("UPDATE products ...)

Это выглядит действительно моложе, но я погуглил и посмотрел вокруг, так и не нашел свой ответ.

Для ясности, у меня есть:

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

Что я хочу:

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

Ответы [ 4 ]

46 голосов
/ 22 марта 2011

Звучит так, как будто вы ищете ActiveRecord :: Base.update_all - из документации:

Обновляет все записи с указанием деталей, если они соответствуют набору предоставленных условий, пределы и порядок также могут быть поставлены.Этот метод создает один оператор SQL UPDATE и отправляет его прямо в базу данных.Он не создает экземпляры задействованных моделей и не запускает обратные вызовы или проверки Active Record.

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

На самом деле, поскольку это rails 2.x (Вы не указали) - цепочка named_scopeможет не работать, вам может понадобиться передать условия для вашей именованной области в качестве второго параметра update_all вместо того, чтобы связывать его в конце области действия Product.

5 голосов
/ 22 марта 2011

Вы пробовали использовать update_all?

http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-update_all

4 голосов
/ 22 марта 2011

Loos вроде 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)")

Обратите внимание, что это зависит от дб - вам может потребоваться настроить стиль цитирования в соответствии с требованиями.

0 голосов
/ 20 июня 2017

Для тех, кому необходимо обновить большое количество записей, миллион или даже больше, есть хороший способ обновления записей по партиям.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...