Оновіть один стовпець до значення іншого в міграції 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.