Обновить один столбец до значения другого при миграции Rails


80

У меня есть таблица в приложении Rails с сотнями тысяч записей, и у них есть только created_atвременная метка. Я добавляю возможность редактировать эти записи, поэтому хочу добавить updated_atметку времени в таблицу. В моей миграции для добавления столбца я хочу обновить все строки, чтобы новые updated_atсоответствовали старым created_at, поскольку это значение по умолчанию для вновь созданных строк в Rails. Я мог бы выполнить find(:all)и перебрать записи, но это заняло бы часы из-за размера таблицы. Я действительно хочу:

UPDATE table_name SET updated_at = created_at;

Есть ли лучший способ сделать это при миграции Rails с использованием ActiveRecord, а не с помощью необработанного SQL?

Ответы:


136

Я бы создал миграцию

rails g migration set_updated_at_values

и внутри напишите что-то вроде:

class SetUpdatedAt < ActiveRecord::Migration
  def self.up
    Yourmodel.update_all("updated_at=created_at")
  end

  def self.down
  end
end

Таким образом вы достигнете двух вещей

  • это повторяемый процесс, при каждом возможном развертывании (где необходимо) он выполняется
  • это эффективно. Я не могу придумать более рубинового решения (столь же эффективного).

Примечание: вы также можете запустить необработанный sql внутри миграции, если запрос становится слишком сложным для записи с использованием activerecord. Просто напишите следующее:

Yourmodel.connection.execute("update your_models set ... <complicated query> ...")

+1 - Мне недавно приходилось делать именно это и использовать SQL поверх ActiveRecord. Это настолько быстро, насколько это возможно.
Питер Браун

40
Yourmodel.update_all 'update_at=created_at'лучше, нет? Это тоже работает на прицеле.
Марк-Андре Лафортюн

Согласно руководству по Rails : «схема базы данных не должна измениться, если вы выполните команду, upза которой следует символ down» . Так что считайте def changeтолько вместо этого.
EliadL

1
@EliadL несколько замечаний: 1) мы не меняем схему, а только содержимое базы данных. И 2) на момент написания этого ответа changeметода еще не существовало, но в этом случае я все же предпочитаю использовать явный upи downболее явный (если вы хотите контролировать, что он downдолжен делать).
nathanvda

20

Вы можете использовать update_all, который работает очень похоже на необработанный SQL. Это все, что у вас есть.

Кстати, лично я не особо обращаю внимание на миграции. Иногда чистый SQL - действительно лучшее решение. Как правило, код миграции повторно не используется. Это разовое действие, поэтому я не беспокоюсь о чистоте кода.


2
Это зависит от ваших потребностей в развертывании. Мне очень нравится использовать миграции, потому что они позволяют многократно развертывать на существующих платформах и получать тот же результат. У нас есть несколько этапов развертывания: разработка, тестирование, проверка / принятие, платформа для подтверждения концепции (для тестирования клиентов), производственная платформа: нам нужно иметь возможность без сбоев переносить существующие данные в новую развернутую версию. Добавление столбца и проверка данных в нашем случае НЕ разовое действие.
nathanvda 07

Я пишу об использовании update_allфайла внутренней миграции :-) Вы также можете выполнить необработанный SQL внутри файла миграции. Однако update_allнемного элегантнее. Оба будут работать одинаково.
Грег Дэн

Обычно это разумная идея объявить модель в миграции, поскольку это предотвратит проблемы, если исходная модель будет переопределена позже. Только что нашел эту статью, в которой все довольно хорошо объясняется: complex-simplicity.com/2010/05/…
François Beausoleil

С update_allя не имею ни малейшего представления о том , как установить значение столбца , что другой, в соответствии с просьбой ОП. Пожалуйста, продемонстрируйте.
nathanvda 08

14

Как писал грегдан, можно использовать update_all. Вы можете сделать что-то вроде этого:

Model.where(...).update_all('updated_at = created_at')

Первая часть - это ваш типичный набор условий. В последней части рассказывается, как выполнять задания. Это приведет к UPDATEутверждению, по крайней мере, в Rails 4.


Это генерируется в 4.2 SET'posts'.'email' = 'options', параметры представляют собой буквальную строку
lulalala

Подтверждение этого совета мне тоже не подходит. Не уверен, что это совершенно неправильное решение. Don't donwvote @martin answer
woto

Вот результат работы консоли Rails:User.update_all('updated_at = created_at') SQL (0.4ms) UPDATE "users" SET updated_at = created_at
Мартин Штрайхер

2

Вы можете напрямую запустить следующую команду на свой rails console ActiveRecord::Base.connection.execute("UPDATE TABLE_NAME SET COL2 = COL1")

Например: я хочу обновить sku моей таблицы элементов с помощью remote_id таблиц элементов. команда будет такой:
ActiveRecord::Base.connection.execute("UPDATE items SET sku = remote_id")


На самом деле, это наиболее безопасный способ "истории", потому что в будущем (когда некоторые будут выполнять миграции, модель Yourmodelуже может быть удалена. Старайтесь избегать использования моделей в миграциях.
Foton

0

Это общий способ решения, не требующий написания запроса, поскольку запросы подвержены риску.

  class Demo < ActiveRecord::Migration
    def change
     add_column :events, :time_zone, :string
     Test.all.each do |p|
       p.update_attributes(time_zone: p.check.last.time_zone)
     end
     remove_column :sessions, :time_zone
    end
  end

-4

В качестве одноразовой операции я бы просто сделал это в rails console. Неужели это займет часы? Может быть, если будут миллионы записей…

records = ModelName.all; records do |r|; r.update_attributes(:updated_at => r.created_at); r.save!; end;`

По сути, это то, что я пробовал в первую очередь, но, поскольку нужно изменить сотни тысяч записей, это займет часы (дни?).
jrdioko 07

Когда я тестировал его, на моей машине разработчика (а не на сервере) было около 50 записей в секунду.
jrdioko 07

4
Всегда избегайте итераций, если это возможно, избегайте использования «all», которое загружает каждую запись в оперативную память сразу, а поскольку update_attributes уже выполняет сохранение автоматически, дополнительный вызов save! сделает всю операцию вдвое дольше.
ryan0
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.