Trova righe con più campi duplicati con Active Record, Rails e Postgres


103

Qual è il modo migliore per trovare record con valori duplicati su più colonne utilizzando Postgres e Activerecord?

Ho trovato questa soluzione qui :

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

Ma non sembra funzionare con postgres. Ricevo questo errore:

PG :: GroupingError: ERRORE: la colonna "parts.id" deve apparire nella clausola GROUP BY o essere utilizzata in una funzione di aggregazione


3
In SQL normale, userei un auto-join, qualcosa di simile select a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id. Non ho idea di come esprimerlo in ActiveRecord-speak.
Craig Ringer

Risposte:


222

Versione testata e funzionante

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

Inoltre, questo è un po 'indipendente ma utile. Se vuoi vedere quante volte è stata trovata ciascuna combinazione, metti .size alla fine:

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

e otterrai un set di risultati simile a questo:

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

Pensavo fosse piuttosto interessante e non l'avevo mai visto prima.

Ringraziamo Taryn, questa è solo una versione ottimizzata della sua risposta.


7
Ho dovuto passare un array esplicito a select()come in: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countper poter funzionare.
Rafael Oliveira

4
aggiungendo i .countPG::UndefinedFunction: ERROR: function count
Magne

1
Puoi provare User.select ([: first,: email]). Group (: first,: email) .having ("count (*)> 1"). Map.count
Serhii Nadolynskyi

3
Sto provando lo stesso metodo ma provando a ottenere anche User.id, aggiungendolo a select e group restituisce un array vuoto. Come posso restituire l'intero modello utente, o almeno includere: id?
Ashbury,

5
utilizzare al .sizeposto di.count
Charles Hamel

32

Questo errore si verifica perché POSTGRES richiede di inserire le colonne di raggruppamento nella clausola SELECT.

provare:

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

(nota: non testato, potrebbe essere necessario modificarlo)

MODIFICATO per rimuovere la colonna ID


7
Non funzionerà; la idcolonna non fa parte del gruppo, quindi non puoi fare riferimento a essa a meno che non la aggreghi (ad esempio array_agg(id)o json_agg(id))
Craig Ringer

9

Se hai bisogno dei modelli completi, prova quanto segue (in base alla risposta di @ newUserNameHere).

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

Ciò restituirà le righe in cui l'indirizzo di posta elettronica della riga non è univoco.

Non sono a conoscenza di un modo per farlo su più attributi.


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

Grazie che funziona alla grande :) Inoltre sembra che l'ultimo .select(:email)sia ridondante. Penso che questo sia un po 'più pulito, ma potrei sbagliarmi. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
chet corey

2

Ottieni tutti i duplicati con una singola query se utilizzi 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

Sulla base della risposta sopra di @newUserNameHere credo che il modo giusto per mostrare il conteggio per ciascuno sia

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

res.each {|r| puts r.attributes } ; nil
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.