Suchen Sie mit Active Record, Rails & Postgres nach Zeilen mit mehreren doppelten Feldern

102

Was ist der beste Weg, um mit Postgres und Activerecord Datensätze mit doppelten Werten über mehrere Spalten hinweg zu finden?

Ich habe diese Lösung hier gefunden :

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

Aber mit Postgres scheint es nicht zu funktionieren. Ich erhalte diesen Fehler:

PG :: GroupingError: ERROR: Die Spalte "parts.id" muss in der GROUP BY-Klausel erscheinen oder in einer Aggregatfunktion verwendet werden

newUserNameHere
quelle
3
In normalem SQL würde ich einen Self-Join verwenden, so etwas wie select a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id. Keine Ahnung, wie man das in ActiveRecord-speak ausdrückt.
Craig Ringer

Antworten:

220

Getestete & Arbeitsversion

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

Auch dies ist ein wenig unabhängig, aber praktisch. Wenn Sie sehen möchten, wie oft jede Kombination gefunden wurde, setzen Sie am Ende .size:

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

und Sie erhalten eine Ergebnismenge, die folgendermaßen aussieht:

{[nil, nil]=>512,
 ["Joe", "[email protected]"]=>23,
 ["Jim", "[email protected]"]=>36,
 ["John", "[email protected]"]=>21}

Ich fand das ziemlich cool und hatte es noch nie gesehen.

Dank an Taryn, dies ist nur eine optimierte Version ihrer Antwort.

newUserNameHere
quelle
7
Ich musste ein explizites Array select()wie in übergeben: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countum zu arbeiten.
Rafael Oliveira
4
Hinzufügen der .countgibtPG::UndefinedFunction: ERROR: function count
Magne
1
Sie können versuchen, User.select ([: first ,: email]). Group (: first ,: email) .having ("count (*)> 1"). Map.count
Serhii Nadolynskyi
3
Ich versuche die gleiche Methode, aber ich versuche auch, die User.id abzurufen. Wenn ich sie zur Auswahl und Gruppe hinzufüge, wird ein leeres Array zurückgegeben. Wie kann ich das gesamte Benutzermodell zurückgeben oder zumindest die: id angeben?
Ashbury
4
Verwenden Sie .sizeanstelle von.count
Charles Hamel
32

Dieser Fehler tritt auf, weil Sie bei POSTGRES Gruppierungsspalten in die SELECT-Klausel einfügen müssen.

Versuchen:

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

(Hinweis: nicht getestet, möglicherweise müssen Sie es optimieren)

BEARBEITET, um die ID-Spalte zu entfernen

Taryn East
quelle
7
Das wird nicht funktionieren; Die idSpalte ist nicht Teil der Gruppe, daher können Sie sie nur referenzieren, wenn Sie sie aggregieren (z. B. array_agg(id)oder json_agg(id))
Craig Ringer
9

Wenn Sie die vollständigen Modelle benötigen, versuchen Sie Folgendes (basierend auf der Antwort von @ newUserNameHere).

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

Dadurch werden die Zeilen zurückgegeben, in denen die E-Mail-Adresse der Zeile nicht eindeutig ist.

Mir ist keine Möglichkeit bekannt, dies über mehrere Attribute hinweg zu tun.

Ben Aubin
quelle
`` `User.where (email: User.select (: email) .group (: email) .having (" count (*)> 1 "))` ``
chet corey
Danke, das funktioniert super :) Scheint auch so, als ob der letzte .select(:email)überflüssig ist. Ich denke, das ist ein bisschen sauberer, aber ich könnte mich irren. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
Chet Corey
2

Holen Sie sich alle Duplikate mit einer einzigen Abfrage, wenn Sie PostgreSQL verwenden :

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
quelle
-1

Basierend auf der obigen Antwort von @newUserNameHere glaube ich, dass der richtige Weg ist, die Anzahl für jeden zu zeigen

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

res.each {|r| puts r.attributes } ; nil
Nuno Costa
quelle