Zaktualizuj jedną kolumnę do wartości innej w migracji Rails


80

Mam tabelę w aplikacji Rails z setkami tysięcy rekordów i mają one tylko created_atznacznik czasu. Dodam możliwość edycji tych rekordów, więc chcę dodać updated_atsygnaturę czasową do tabeli. Podczas migracji, aby dodać kolumnę, chcę zaktualizować wszystkie wiersze, aby nowy updated_atpasował do starego created_at, ponieważ jest to domyślne dla nowo utworzonych wierszy w Railsach. Mógłbym zrobić find(:all)i iterować przez rekordy, ale zajęłoby to godziny ze względu na rozmiar tabeli. To, co naprawdę chcę zrobić, to:

UPDATE table_name SET updated_at = created_at;

Czy istnieje lepszy sposób na zrobienie tego w migracji Railsów przy użyciu ActiveRecord zamiast wykonywania surowego SQL?

Odpowiedzi:


136

Stworzyłbym migrację

rails g migration set_updated_at_values

aw środku napisz coś takiego:

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

  def self.down
  end
end

W ten sposób osiągniesz dwie rzeczy

  • jest to powtarzalny proces, który jest wykonywany przy każdym możliwym wdrożeniu (w razie potrzeby)
  • to jest wydajne. Nie mogę wymyślić bardziej rubinowego rozwiązania (tak wydajnego).

Uwaga: możesz również uruchomić surowy sql wewnątrz migracji, jeśli zapytanie stanie się zbyt trudne do napisania przy użyciu activerecord. Po prostu napisz:

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

+1 - ostatnio musiałem zrobić dokładnie to i użyłem SQL na ActiveRecord. Jest tak szybki, jak to tylko możliwe.
Peter Brown

40
Yourmodel.update_all 'update_at=created_at'jest ładniej, nie? Działa też na zakres.
Marc-André Lafortune

Zgodnie z przewodnikiem po Railsach : „schemat bazy danych powinien pozostać niezmieniony, jeśli wykonasz a, uppo którym nastąpi a down . Więc rozważ def changetylko zamiast tego.
EliadL

1
@EliadL kilka uwag: 1) nie zmieniamy schematu, tylko zawartość bazy danych. I 2) w czasie, gdy pisano tę odpowiedź, changemetoda jeszcze nie istniała, ale w tym przypadku nadal wolę używać wyrażenia jawnego upi downbyć bardziej wyraźnym (jeśli chcesz kontrolować, co downnależy zrobić).
nathanvda

20

Możesz użyć update_all, który działa bardzo podobnie do surowego SQL. To wszystkie opcje, które masz.

Swoją drogą osobiście nie przywiązuję tak dużej wagi do migracji. Czasami surowy SQL jest naprawdę najlepszym rozwiązaniem. Zwykle kod migracji nie jest ponownie używany. To jest jednorazowa akcja, więc nie przejmuję się czystością kodu.


2
To zależy od Twoich potrzeb związanych z wdrożeniem. Bardzo lubię korzystać z migracji, ponieważ pozwalają na powtarzalne wdrażanie na istniejących platformach i dają ten sam efekt. Mamy kilka etapów wdrażania: dev, test, qa / akceptacja, platforma proof of concept (do testowania klientów), platforma produkcyjna: musimy mieć możliwość bezbłędnej migracji istniejących danych do nowo wdrożonej wersji. Dodanie kolumny i upewnienie się, że dane są prawidłowe, w naszym przypadku NIE jest jednorazową czynnością.
nathanvda,

Piszę o używaniu update_allwewnątrz pliku migracyjnego :-) Można również wykonać surowy SQL w pliku migracyjnym. Jednak update_alljest trochę bardziej elegancki. Oba będą działać dokładnie tak samo.
Greg Dan,

Zwykle dobrym pomysłem jest zadeklarowanie modelu podczas migracji, ponieważ zapobiegnie to problemom, jeśli oryginalny model zostanie później przedefiniowany. Właśnie znalazłem ten artykuł, który wyjaśnia wszystko całkiem ładnie: skomplikowane-simplicity.com/2010/05/…
François Beausoleil

Nie update_allmam pojęcia, jak ustawić wartość kolumny na inną, zgodnie z żądaniem PO. Proszę zademonstrować.
nathanvda

14

Jak napisał gregdan, możesz użyć update_all. Możesz zrobić coś takiego:

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

Pierwsza część to typowy zestaw warunków. Ostatnia część mówi, jak wykonać zadania. Spowoduje to wyświetlenie UPDATEinstrukcji, przynajmniej w Rails 4.


To w 4.2 generuje SET'posts'.'email' = 'options', opcje to dosłowny ciąg
lulalala

Potwierdzenie tej wskazówki też nie działa dla mnie. Nie jestem pewien, czy to całkowicie błędne rozwiązanie. Don't donwvote @martin answer
woto

Oto wynik z konsoli Railsów:User.update_all('updated_at = created_at') SQL (0.4ms) UPDATE "users" SET updated_at = created_at
Martin Streicher,

2

Możesz bezpośrednio uruchomić następujące polecenie do swojego rails console ActiveRecord::Base.connection.execute("UPDATE TABLE_NAME SET COL2 = COL1")

Na przykład: Chcę zaktualizować SKU mojej tabeli elementów o zdalny_id tabel elementów. polecenie będzie następujące:
ActiveRecord::Base.connection.execute("UPDATE items SET sku = remote_id")


W rzeczywistości jest to zdecydowanie najbardziej bezpieczny sposób w historii, ponieważ w przyszłości (gdy niektórzy będą uruchamiać migracje, model Yourmodelmożna już usunąć. Staraj się unikać używania modeli w migracjach).
Foton

0

Jest to ogólny sposób rozwiązywania problemów, bez konieczności pisania zapytań, ponieważ zapytania są narażone na ryzyko.

  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

Jako operacja jednorazowa zrobiłbym to po prostu w rails console. Czy to naprawdę zajmie godziny? Może jeśli są miliony rekordów…

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

Zasadniczo tego próbowałem najpierw, ale ponieważ istnieją setki tysięcy rekordów, które wymagają zmiany, zajmie to godziny (dni?).
jrdioko

Kiedy go testowałem, na mojej maszynie deweloperskiej (nie na serwerze) było to około 50 rekordów na sekundę.
jrdioko

4
Zawsze unikaj iteracji, jeśli to możliwe, unikaj używania 'all', który ładuje każdy rekord od razu do pamięci RAM, a ponieważ update_attributes już zapisuje automatycznie, dodatkowe wywołanie save! sprawi, że cała operacja potrwa dwa razy dłużej.
ryan0
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.