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

newUserNameHere
źródło
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", "[email protected]"]=>23,
 ["Jim", "[email protected]"]=>36,
 ["John", "[email protected]"]=>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.

newUserNameHere
źródło
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

Taryn East
źródło
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.

Ben Aubin
źródło
`` `` 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
itsnikolay
źródło
-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
Nuno Costa
źródło