2013-01-02 19 views
66

Używam Heroku do obsługi mojej aplikacji Ruby on Rails iz tego czy innego powodu, mogę mieć kilka zduplikowanych wierszy.Usunąć zduplikowane rekordy na podstawie wielu kolumn?

Czy istnieje sposób usuwania duplikatów rekordów w oparciu o 2 lub więcej kryteriów, ale zachowaj tylko 1 rekord tej duplikowanej kolekcji?

W moim przypadku użycia, mam relację Marka i Model dla samochodów w mojej bazie danych.

Make  Model 
---  --- 
Name  Name 
      Year 
      Trim 
      MakeId 

Chciałbym usunąć wszystkie rekordy modelu, które mają taką samą nazwę, rok i Przycinanie ale zachować 1 tych zapisów (co oznacza, muszę rekord ale tylko raz). Używam konsoli Heroku, dzięki czemu mogę łatwo uruchamiać aktywne zapytania dotyczące rekordów.

Wszelkie sugestie?

Odpowiedz

122
class Model 

    def self.dedupe 
    # find all models and group them on keys which should be common 
    grouped = all.group_by{|model| [model.name,model.year,model.trim,model.make_id] } 
    grouped.values.each do |duplicates| 
     # the first one we want to keep right? 
     first_one = duplicates.shift # or pop for last one 
     # if there are any more left, they are duplicates 
     # so delete all of them 
     duplicates.each{|double| double.destroy} # duplicates can now be destroyed 
    end 
    end 

end 

Model.dedupe 
  • Znajdź wszystkie
  • Grupa je na klucze, które trzeba na wyjątkowość
  • pętli na wartościach zgrupowanych modelu z hash
  • usunąć pierwszą wartość, ponieważ chcesz zachować jedną kopię
  • usuń resztę
+0

jest w modelu modelu? – meetalexjohnson

+0

@meetalexjohnson powinien być w dowolnym modelu aktywnego rekordu, który masz. –

+1

to skały, dziękuję! –

41

Jeśli dane tabeli użytkownika, jak poniżej

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 > 

Identyfikatory e-mail są duplikowane, więc naszym celem jest usunięcie wszystkich duplikatów identyfikatorów e-mail z tabeli użytkowników.

Krok 1:

Aby uzyskać wszystkie rekordy e-mail odrębny identyfikator.

ids = User.select("MIN(id) as id").group(:email,:name).collect(&:id) 
=> [15, 16, 18, 19, 17] 

Krok 2:

Aby usunąć duplikaty z tabeli identyfikatorów użytkownika z rekordami odrębny e-mail id.

Teraz tablica ids zawiera następujące identyfikatory.

[15, 16, 18, 19, 17] 
User.where("id NOT IN (?)",ids) # To get all duplicate records 
User.where("id NOT IN (?)",ids).destroy_all 

SZYNY 4 ** **

ActiveRecord 4 wprowadza metodę .not który pozwala pisać o następujących Krok 2:

User.where.not(id: ids).destroy_all 
+0

Dzięki, pomogło mi to !! –

+0

nie działa grupowanie .... z wyjątkiem –

0

można spróbować tej kwerendy SQL, aby usunąć wszystkie zduplikowane rekordy, ale najnowszy

DELETE FROM users USING users user WHERE (users.name = user.name AND users.year = user.year AND users.trim = user.trim AND users.id < user.id); 
+0

Spowoduje to usunięcie wszystkich. – monteirobrena

9

Podobny do @Aditya Sanghi Odpowiedź jest, ale w ten sposób bardziej wydajna, ponieważ wybierasz tylko duplikaty, zamiast ładowania każdego obiektu Model do pamięci, a następnie iterowania po nich wszystkich.

# 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 

Ponadto, jeśli naprawdę nie chcesz zduplikowane dane w tej tabeli, prawdopodobnie chcesz dodać multi-kolumna unikatowy indeks do stołu, coś wzdłuż linii:

add_index :models, [:name, :year, :trim], unique: true, name: 'index_unique_models' 
2

aby uruchomić go na migrację I zakończyła się robi jak poniżej (na podstawie answer above przez @ 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 
Powiązane problemy