Intereting Posts
ORA-12015: невозможно создать быстрый обновленный материальный вид из сложного запроса Преобразование SQL-запроса (с корреляционным подзапросом) в LINQ в C # Число строк в результатах запроса с использованием Microsoft Access Обновление столбца в одной таблице через столбец в другой таблице Сохраненная процедура, имя таблицы прохода в качестве параметра SQL-запрос, который дает различные результаты, соответствующие нескольким столбцам получить последнюю запись для каждого идентификатора Лучшие методы управления изменениями базы данных mysql-процедура для обновления числовой ссылки в предыдущих строках при обновлении Получение счета двух разных наборов строк в таблице, а затем их разделение T-SQL: использование CASE в инструкции UPDATE для обновления определенных столбцов в зависимости от условия Проблема Hibernate HQL, ожидающая, что IDENT обнаружит «*», Предложите другие запросы для архаичных написаний (например, «Вы имели в виду Google») Установите максимальное значение столбца в качестве значения начала последовательности с помощью тегов ликбазы Получите последнюю строку различного идентификатора, затем отобразите данные, которые больше нуля

Как я могу запускать обновления в пакетах в Rails 3/4?

Мне нужно массовое обновление многих тысяч записей, и я хотел бы обрабатывать обновления в пакетах. Во-первых, я попробовал:

Foo.where(bar: 'bar').find_in_batches.update_all(bar: 'baz') 

… который я надеялся создать SQL, например:

 "UPDATE foo SET bar = 'baz' where bar='bar' AND id > (whatever id is passed in by find_in_batches)" 

Это не работает, потому что find_in_batches возвращает массив, а update_all – отношение ActiveRecord.

Вот что я пробовал дальше:

 Foo.where(bar: 'bar').select('id').find_in_batches do |foos| ids = foos.map(&:id) Foo.where(id: ids).update_all(bar: 'baz') end 

Это работает, но, очевидно, выполняется выбор, за которым следует обновление, а не одно обновление, основанное на моих условиях «где». Есть ли способ очистить это, так что выбор и обновление не должны быть отдельными запросами?

В Rails 5 есть новый удобный метод ActiveRecord::Relation#in_batches для решения этой проблемы:

 Foo.in_batches.update_all(bar: 'baz') 

Проверьте документацию .

Я тоже удивлен, что нет более простого способа сделать это … но я придумал такой подход:

 batch_size = 1000 0.step(Foo.count, batch_size).each do |offset| Foo.where(bar: 'bar').order(:id) .offset(offset) .limit(batch_size) .update_all(bar: 'baz') end 

В основном это будет:

  1. Создайте массив смещений между 0 и Foo.count stepping by batch_size каждый раз. Например, если Foo.count == 10500 вы получите: [0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]
  2. Пронумеруйте эти числа и используйте их как СМЕЩЕНИЕ в SQL-запросе, обязательно заказывая по id и ограничивая batch_size .
  3. Обновление не более записей batch_size чей «индекс» больше offset .

Это в основном ручной способ выполнить то, что вы сказали, на что надеялись в сгенерированном SQL. Слишком плохо, что это невозможно сделать уже по стандартным библиотечным методам … хотя я уверен, что вы могли бы создать свой собственный.

Ответ pdobb на правильном пути, но не работал для меня в Rails 3.2.21 из-за этой проблемы ActiveRecord, не разбирающей OFFSET с вызовами UPDATE:

https://github.com/rails/rails/issues/10849

Я изменил код соответственно, и он отлично работал для одновременного установки значения по умолчанию в моей таблице Postgres:

 batch_size = 1000 0.step(Foo.count, batch_size).each do |offset| Foo.where('id > ? AND id <= ?', offset, offset + batch_size). order(:id). update_all(foo: 'bar') end 

Это на 2 года позже, но ответы здесь: a) очень медленны для больших наборов данных и b) игнорируют возможности встроенных рельсов ( http://api.rubyonrails.org/classes/ActiveRecord/Batches.html ).

По мере увеличения значения смещения, в зависимости от вашего сервера БД, он будет выполнять сканирование последовательности до тех пор, пока не достигнет вашего блока, а затем извлечет данные для обработки. Поскольку ваше смещение попадает в миллионы, это будет очень медленно.

используйте метод итератора «find_each»:

 Foo.where(a: b).find_each do |bar| bar.x = y bar.save end 

Это дает дополнительное преимущество запуска обратных вызовов модели при каждом сохранении. Если вам не нужны обратные вызовы, попробуйте:

 Foo.where(a: b).find_in_batches do |array_of_foo| ids = array_of_foo.collect &:id Foo.where(id: ids).update_all(x: y) end 

Я написал небольшой метод для вызова update_all пакетами:

https://gist.github.com/VarunNatraaj/420c638d544be59eef85

Надеюсь, это полезно! 🙂

У вас не было возможности проверить это, но вы могли бы использовать AREL и дополнительный запрос.

 Foo.where(bar: 'bar').select('id').find_in_batches do |foos| Foo.where( Foo.arel_table[ :id ].in( foos.to_arel ) ).update_all(bar: 'baz') end