Ich verwende Heroku, um meine Ruby on Rails-Anwendung zu hosten, und aus dem einen oder anderen Grund habe ich möglicherweise doppelte Zeilen.
Gibt es eine Möglichkeit, doppelte Datensätze basierend auf zwei oder mehr Kriterien zu löschen, aber nur einen Datensatz dieser doppelten Sammlung zu behalten?
In meinem Anwendungsfall habe ich eine Marken- und Modellbeziehung für Autos in meiner Datenbank.
Make Model
--- ---
Name Name
Year
Trim
MakeId
Ich möchte alle Modelldatensätze löschen, die denselben Namen, dasselbe Jahr und denselben Schnitt haben, aber einen dieser Datensätze behalten (dh ich benötige den Datensatz nur einmal). Ich verwende die Heroku-Konsole, damit ich problemlos einige aktive Datensatzabfragen ausführen kann.
Irgendwelche Vorschläge?
ruby-on-rails-3
activerecord
duplicates
destroy
sergserg
quelle
quelle
Wenn Ihre Benutzertabelle Daten wie unten
User.all => [ #<User id: 15, name: "a", email: "[email protected]", created_at: "2013-08-06 08:57:09", updated_at: "2013-08-06 08:57:09">, #<User id: 16, name: "a1", email: "[email protected]", created_at: "2013-08-06 08:57:20", updated_at: "2013-08-06 08:57:20">, #<User id: 17, name: "b", email: "[email protected]", created_at: "2013-08-06 08:57:28", updated_at: "2013-08-06 08:57:28">, #<User id: 18, name: "b1", email: "[email protected]", created_at: "2013-08-06 08:57:35", updated_at: "2013-08-06 08:57:35">, #<User id: 19, name: "b11", email: "[email protected]", created_at: "2013-08-06 09:01:30", updated_at: "2013-08-06 09:01:30">, #<User id: 20, name: "b11", email: "[email protected]", created_at: "2013-08-06 09:07:58", updated_at: "2013-08-06 09:07:58">] 1.9.2p290 :099 >
E-Mail-IDs sind doppelt vorhanden. Daher ist es unser Ziel, alle doppelten E-Mail-IDs aus der Benutzertabelle zu entfernen.
Schritt 1:
Um alle eindeutigen E-Mail-Datensatz-ID zu erhalten.
ids = User.select("MIN(id) as id").group(:email,:name).collect(&:id) => [15, 16, 18, 19, 17]
Schritt 2:
So entfernen Sie doppelte IDs aus der Benutzertabelle mit einer eindeutigen E-Mail-Datensatz-ID.
Jetzt enthält das IDs-Array die folgenden IDs.
[15, 16, 18, 19, 17] User.where("id NOT IN (?)",ids) # To get all duplicate records User.where("id NOT IN (?)",ids).destroy_all
** SCHIENEN 4 **
ActiveRecord 4 führt die
.not
Methode ein, mit der Sie in Schritt 2 Folgendes schreiben können:User.where.not(id: ids).destroy_all
quelle
Ähnlich wie die Antwort von @Aditya Sanghi, aber dieser Weg ist leistungsfähiger, da Sie nur die Duplikate auswählen, anstatt jedes Modellobjekt in den Speicher zu laden und dann alle zu durchlaufen.
# returns only duplicates in the form of [[name1, year1, trim1], [name2, year2, trim2],...] duplicate_row_values = Model.select('name, year, trim, count(*)').group('name, year, trim').having('count(*) > 1').pluck(:name, :year, :trim) # load the duplicates and order however you wantm and then destroy all but one duplicate_row_values.each do |name, year, trim| Model.where(name: name, year: year, trim: trim).order(id: :desc)[1..-1].map(&:destroy) end
Wenn Sie wirklich keine doppelten Daten in dieser Tabelle möchten, möchten Sie der Tabelle wahrscheinlich einen mehrspaltigen eindeutigen Index hinzufügen, der wie folgt aussieht:
add_index :models, [:name, :year, :trim], unique: true, name: 'index_unique_models'
quelle
Sie können Folgendes versuchen: (basierend auf vorherigen Antworten)
ids = Model.group('name, year, trim').pluck('MIN(id)')
um alle gültigen Datensätze zu erhalten. Und dann:
Model.where.not(id: ids).destroy_all
um die nicht benötigten Datensätze zu entfernen. Und natürlich können Sie eine Migration durchführen, die einen eindeutigen Index für die drei Spalten hinzufügt, damit dies auf DB-Ebene erzwungen wird:
add_index :models, [:name, :year, :trim], unique: true
quelle
Um es auf einer Migration auszuführen, habe ich am Ende Folgendes getan (basierend auf der obigen Antwort von @ aditya-sanghi)
class AddUniqueIndexToXYZ < ActiveRecord::Migration def change # delete duplicates dedupe(XYZ, 'name', 'type') add_index :xyz, [:name, :type], unique: true end def dedupe(model, *key_attrs) model.select(key_attrs).group(key_attrs).having('count(*) > 1').each { |duplicates| dup_rows = model.where(duplicates.attributes.slice(key_attrs)).to_a # the first one we want to keep right? dup_rows.shift dup_rows.each{ |double| double.destroy } # duplicates can now be destroyed } end end
quelle
model.unscoped
Abfragen hinzufügen, um zu vermeiden, dass der Standardbereich in der aktuellen Gruppenabfrage nicht vorhanden ist.Basierend auf der Antwort von @ aditya-sanghi , mit einer effizienteren Möglichkeit, Duplikate mithilfe von SQL zu finden.
Fügen Sie dies zu Ihrem hinzu
ApplicationRecord
, um jedes Modell deduplizieren zu können:class ApplicationRecord < ActiveRecord::Base # … def self.destroy_duplicates_by(*columns) groups = select(columns).group(columns).having(Arel.star.count.gt(1)) groups.each do |duplicates| records = where(duplicates.attributes.symbolize_keys.slice(*columns)) records.offset(1).destroy_all end end end
Sie können dann aufrufen
destroy_duplicates_by
, um alle Datensätze (außer dem ersten) zu zerstören, die dieselben Werte für die angegebenen Spalten haben. Zum Beispiel:Model.destroy_duplicates_by(:name, :year, :trim, :make_id)
quelle
Sie können diese SQL-Abfrage versuchen, um alle doppelten Datensätze außer dem neuesten zu entfernen
quelle