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


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", "test@test.com"]=>23,
 ["Jim", "email2@gmail.com"]=>36,
 ["John", "email3@gmail.com"]=>21}

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

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


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


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.


`` `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

-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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.