Znajdź wiersze z wieloma zduplikowanymi polami za pomocą Active Record, Rails i Postgres


103

Jaki jest najlepszy sposób na znalezienie rekordów ze zduplikowanymi wartościami w wielu kolumnach przy użyciu Postgres i Activerecord?

Znalazłem to rozwiązanie tutaj :

User.find(:all, :group => [:first, :email], :having => "count(*) > 1" )

Ale wygląda na to, że nie działa z postgresami. Otrzymuję ten błąd:

PG :: GroupingError: ERROR: kolumna „parts.id” musi występować w klauzuli GROUP BY lub być używana w funkcji agregującej


3
W zwykłym SQL użyłbym samosprzężenia, coś w rodzaju select a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id. Nie mam pojęcia, jak wyrazić to w języku ActiveRecord.
Craig Ringer

Odpowiedzi:


225

Wersja testowana i działająca

User.select(:first,:email).group(:first,:email).having("count(*) > 1")

Jest to również trochę niezwiązane, ale przydatne. Jeśli chcesz zobaczyć, ile razy znaleziono każdą kombinację, wpisz .size na końcu:

User.select(:first,:email).group(:first,:email).having("count(*) > 1").size

a otrzymasz zestaw wyników, który wygląda następująco:

{[nil, nil]=>512,
 ["Joe", "test@test.com"]=>23,
 ["Jim", "email2@gmail.com"]=>36,
 ["John", "email3@gmail.com"]=>21}

Pomyślałem, że to całkiem fajne i wcześniej tego nie widziałem.

Podziękowania dla Taryn, to tylko poprawiona wersja jej odpowiedzi.


7
Musiałem przekazać jawną tablicę do select()jak w: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countaby działać.
Rafael Oliveira

4
dodanie .countdajePG::UndefinedFunction: ERROR: function count
Magne

1
Możesz spróbować User.select ([: first,: email]). Group (: first,: email) .having ("count (*)> 1"). Map.count
Serhii Nadolynskyi

3
Próbuję tej samej metody, ale próbuję również uzyskać identyfikator User.id, dodając go do zaznaczenia, a grupa zwraca pustą tablicę. Jak mogę zwrócić cały model użytkownika lub przynajmniej dołączyć: id?
Ashbury

6
użyj .sizezamiast.count
Charles Hamel

33

Ten błąd występuje, ponieważ POSTGRES wymaga umieszczenia kolumn grupujących w klauzuli SELECT.

próbować:

User.select(:first,:email).group(:first,:email).having("count(*) > 1").all

(uwaga: nie testowane, może być konieczne dostosowanie)

ZMIENIONO, aby usunąć kolumnę z identyfikatorem


7
To nie zadziała; idkolumna nie jest częścią grupy, więc nie można przekazać ją chyba agregować go (np array_agg(id)lub json_agg(id))
Craig Ringer

10

Jeśli potrzebujesz pełnych modeli, wypróbuj następujące rozwiązania (w oparciu o odpowiedź @ newUserNameHere).

User.where(email: User.select(:email).group(:email).having("count(*) > 1").select(:email))

Spowoduje to zwrócenie wierszy, w których adres e-mail wiersza nie jest unikalny.

Nie znam sposobu, aby to zrobić w przypadku wielu atrybutów.


`` `` User.where (email: User.select (: email) .group (: email) .having ("count (*)> 1")) ``
chet corey

Dziękuję, że działa świetnie :) Wygląda na to, że ostatni .select(:email)jest zbędny. Myślę, że to trochę czystsze, ale mogę się mylić. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
chet corey

Dzięki za szybkie obejście.
RanaAlie

3

Uzyskaj wszystkie duplikaty za pomocą jednego zapytania, jeśli używasz PostgreSQL :

def duplicated_users
  duplicated_ids = User
    .group(:first, :email)
    .having("COUNT(*) > 1")
    .select('unnest((array_agg("id"))[2:])')

  User.where(id: duplicated_ids)
end

irb> duplicated_users

-1

Opierając się na powyższej odpowiedzi @newUserName, uważam, że właściwy sposób pokazania liczby dla każdego z nich to

res = User.select('first, email, count(1)').group(:first,:email).having('count(1) > 1')

res.each {|r| puts r.attributes } ; nil
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.