Aktualisieren Sie eine Spalte bei der Rails-Migration auf den Wert einer anderen

79

Ich habe eine Tabelle in einer Rails-App mit Hunderttausenden von Datensätzen, und sie haben nur einen created_atZeitstempel. Ich füge die Möglichkeit hinzu, diese Datensätze zu bearbeiten, daher möchte ich updated_atder Tabelle einen Zeitstempel hinzufügen . Bei meiner Migration zum Hinzufügen der Spalte möchte ich alle Zeilen aktualisieren, damit die neuen updated_atmit den alten übereinstimmen created_at, da dies die Standardeinstellung für neu erstellte Zeilen in Rails ist. Ich könnte ein find(:all)und die Datensätze durchlaufen, aber das würde wegen der Größe der Tabelle Stunden dauern. Was ich wirklich tun möchte ist:

UPDATE table_name SET updated_at = created_at;

Gibt es eine bessere Möglichkeit, dies bei einer Rails-Migration mit ActiveRecord zu tun, als Raw SQL auszuführen?

jrdioko
quelle

Antworten:

133

Ich würde eine Migration erstellen

rails g migration set_updated_at_values

und drin schreibe so etwas wie:

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

  def self.down
  end
end

Auf diese Weise erreichen Sie zwei Dinge

  • Dies ist ein wiederholbarer Prozess, bei dem jede mögliche Bereitstellung (wo erforderlich) ausgeführt wird
  • das ist effizient. Ich kann mir keine rubinrotere Lösung vorstellen (die genauso effizient ist).

Hinweis: Sie können Raw-SQL auch innerhalb einer Migration ausführen, wenn die Abfrage mit activerecord zu schwer zu schreiben ist. Schreiben Sie einfach Folgendes:

Yourmodel.connection.execute("update your_models set ... <complicated query> ...")
Nathanvda
quelle
+1 - Ich musste kürzlich genau dies tun und habe SQL über einen ActiveRecord verwendet. Es ist so schnell wie es nur geht.
Peter Brown
40
Yourmodel.update_all 'update_at=created_at'ist schöner, nein? Es funktioniert auch in einem Bereich.
Marc-André Lafortune
Laut Rails-Handbuch : "Das Datenbankschema sollte unverändert bleiben, wenn Sie ein upgefolgt von einem downausführen . " Also def changenur stattdessen überlegen .
EliadL
1
@EliadL ein paar Bemerkungen: 1) Wir ändern nicht das Schema, nur den Inhalt der Datenbank. Und 2) zu dem Zeitpunkt, als diese Antwort geschrieben wurde, existierte die changeMethode noch nicht, aber in diesem Fall bevorzuge ich es immer noch, explizit zu verwenden upund expliziter downzu sein (wenn Sie steuern möchten, was das downtun soll).
Nathanvda
20

Sie können update_all verwenden, das sehr ähnlich zu Raw SQL funktioniert. Das sind alles Möglichkeiten, die Sie haben.

Übrigens schenke ich Migrationen nicht so viel Aufmerksamkeit. Manchmal ist Raw SQL wirklich die beste Lösung. Im Allgemeinen wird Migrationscode nicht wiederverwendet. Dies ist eine einmalige Aktion, daher kümmere ich mich nicht um die Reinheit des Codes.

Greg Dan
quelle
2
Das hängt von Ihren Bereitstellungsanforderungen ab. Ich verwende Migrationen sehr gerne, da sie eine wiederholbare Bereitstellung auf vorhandenen Plattformen ermöglichen und das gleiche Ergebnis erzielen. Wir haben mehrere Bereitstellungsphasen: dev, test, qa / accept, eine Proof-of-Concept-Plattform (zum Testen von Clients), eine Produktionsplattform: Wir müssen in der Lage sein, vorhandene Daten fehlerfrei auf die neu bereitgestellte Version zu migrieren. Das Hinzufügen einer Spalte und das Sicherstellen, dass die Daten in Ordnung sind, ist in unserem Fall KEINE einmalige Aktion.
Nathanvda
Ich schreibe über die Verwendung update_allinnerhalb der Migrationsdatei :-) Sie können auch Roh-SQL innerhalb der Migrationsdatei ausführen. Ist update_allaber etwas eleganter. Beide werden genau die gleiche Leistung erbringen.
Greg Dan
Es ist normalerweise eine kluge Idee, das Modell in der Migration zu deklarieren, da dies Probleme verhindert, wenn das ursprüngliche Modell später neu definiert wird. Habe gerade diesen Artikel gefunden, der alles ganz gut erklärt: kompliziert-simplicity.com/2010/05/…
François Beausoleil
Mit habe update_allich keine Ahnung, wie man einen Wert der Spalte auf den eines anderen setzt, wie es das OP verlangt. Bitte demonstrieren Sie.
Nathanvda
14

Wie Gregdan schrieb, können Sie verwenden update_all. Sie können so etwas tun:

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

Der erste Teil ist Ihr typischer Satz von Bedingungen. Der letzte Teil beschreibt, wie die Zuweisungen vorgenommen werden. Dies ergibt UPDATEzumindest in Rails 4 eine Aussage.

Martin Streicher
quelle
Dies in 4.2 generiert SET'posts'.'email' = 'options', die Optionen ist eine wörtliche Zeichenfolge
lulalala
Das Bestätigen dieses Tippes funktioniert auch bei mir nicht. Unsicher, dass es eine völlig falsche Lösung ist. Sie donwvote nicht @ Martin Antwort
Woto
Hier ist die Ausgabe von der Rails-Konsole:User.update_all('updated_at = created_at') SQL (0.4ms) UPDATE "users" SET updated_at = created_at
Martin Streicher
2

Sie können den folgenden Befehl direkt auf Ihrem ausführen rails console ActiveRecord::Base.connection.execute("UPDATE TABLE_NAME SET COL2 = COL1")

Beispiel: Ich möchte die SKU meiner Elementtabelle mit remote_id der Elementtabellen aktualisieren. Der Befehl lautet wie folgt:
ActiveRecord::Base.connection.execute("UPDATE items SET sku = remote_id")

Sarwan Kumar
quelle
Tatsächlich ist dies bei weitem der sicherste Weg, da in Zukunft (wenn einige Migrationen ausführen, das Modell Yourmodelbereits gelöscht werden kann. Versuchen Sie, die Verwendung von Modellen in Migrationen zu vermeiden.
Foton
0

Dies ist eine allgemeine Methode zum Lösen, ohne dass eine Abfrage geschrieben werden muss, da Abfragen einem Risiko ausgesetzt sind.

  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
Wilson Varghese
quelle
-4

Als einmalige Operation würde ich es einfach in der machen rails console. Wird es wirklich Stunden dauern? Vielleicht, wenn es Millionen von Datensätzen gibt ...

records = ModelName.all; records do |r|; r.update_attributes(:updated_at => r.created_at); r.save!; end;`
Ghoppe
quelle
Das habe ich im Wesentlichen zuerst versucht, aber da Hunderttausende von Datensätzen geändert werden müssen, dauert dies Stunden (Tage?).
Jrdioko
Als ich es getestet habe, waren es ungefähr 50 Datensätze pro Sekunde auf meinem Entwicklercomputer (kein Server).
Jrdioko
4
Vermeiden Sie nach Möglichkeit immer die Iteration, vermeiden Sie die Verwendung von 'all', die jeden Datensatz auf einmal in den RAM lädt, und da update_attributes bereits automatisch speichert, ist der zusätzliche Aufruf zum Speichern! Dadurch dauert der gesamte Vorgang doppelt so lange.
Ryan0