Оновіть один стовпець до значення іншого в міграції 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'приємніше, ні? Це також працює на область.
Marc-André Lafortune

Згідно з керівництвом Rails : "схема бази даних повинна бути незмінною, якщо ви зробите upпослідовність down" . Тож розглядайте def changeлише замість цього.
EliadL

1
@EliadL кілька зауважень: 1) ми не змінюємо схему, а лише вміст бази даних. І 2) на момент написання цієї відповіді changeметоду ще не існувало, але в цьому випадку я все одно вважаю за краще використовувати явний текст upі downбути більш явним (якщо ви хочете контролювати, що downслід робити).
nathanvda

20

Ви можете використовувати update_all який працює дуже схоже на вихідний SQL. Це всі варіанти, які у вас є.

До речі, я не приділяю такої великої уваги міграціям. Іноді сирий SQL справді є найкращим рішенням. Зазвичай код міграції не використовується повторно. Це одноразова дія, тому я не турбуюся про чистоту коду.


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

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

Зазвичай розумна ідея оголосити модель під час міграції, оскільки це запобіжить виникненню проблем, якщо вихідну модель буде перевизначено пізніше. Щойно знайшов цю статтю, яка цілком добре пояснює все: complex-simplicity.com/2010/05/…
Франсуа Босолей

З update_allя не уявляю, як встановити значення стовпця для значення іншого, як вимагав OP. Будь ласка, продемонструйте.
nathanvda

14

Як писав Грегдан, можна використовувати update_all. Ви можете зробити щось подібне:

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

Перша порція - це ваш типовий набір умов. Остання частина розповідає, як виконувати завдання. Це дасть UPDATEзаяву, принаймні в Rails 4.


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

Підтвердження цієї підказки не працює і для мене. Не переконайтесь, що це абсолютно неправильне рішення. Не голосуйте за @martin відповідь
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вже може бути видалена. Намагайтеся уникати використання моделей у міграціях.
Фотон,

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.03.11

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

4
Завжди уникайте ітерацій, якщо це можливо, уникайте використання "все", яке завантажує кожен запис в оперативну пам'ять одночасно, а оскільки update_attributes вже робить збереження автоматично, додатковий виклик для збереження! змусить всю операцію зайняти вдвічі більше часу.
ryan0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.