Encuentre filas con múltiples campos duplicados con Active Record, Rails y Postgres

103

¿Cuál es la mejor manera de encontrar registros con valores duplicados en múltiples columnas usando Postgres y Activerecord?

Encontré esta solución aquí :

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

Pero no parece funcionar con postgres. Recibo este error:

PG :: GroupingError: ERROR: la columna "parts.id" debe aparecer en la cláusula GROUP BY o usarse en una función agregada

newUserNameHere
fuente
3
En SQL normal, usaría una autounión, algo como select a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id. No tengo idea de cómo expresar eso en ActiveRecord-speak.
Craig Ringer

Respuestas:

222

Versión probada y funcional

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

Además, esto no está relacionado pero es útil. Si desea ver cuántas veces se encontró cada combinación, coloque .size al final:

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

y obtendrás un conjunto de resultados que se ve así:

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

Pensé que era bastante bueno y no lo había visto antes.

Gracias a Taryn, esta es solo una versión modificada de su respuesta.

newUserNameHere
fuente
7
Tuve que pasar una matriz explícita a select()como en: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countpara que funcione.
Rafael Oliveira
4
agregando las .countdonacionesPG::UndefinedFunction: ERROR: function count
Magne
1
Puede probar User.select ([: first,: email]). Group (: first,: email) .having ("count (*)> 1"). Map.count
Serhii Nadolynskyi
3
Estoy probando el mismo método pero tratando de obtener el User.id también, agregarlo a la selección y el grupo devuelve una matriz vacía. ¿Cómo puedo devolver el modelo de usuario completo, o al menos incluir el: id?
Ashbury
5
uso en .sizelugar de.count
Charles Hamel
32

Ese error se produce porque POSTGRES requiere que coloque columnas de agrupación en la cláusula SELECT.

tratar:

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

(nota: no probado, es posible que deba modificarlo)

EDITADO para eliminar la columna de identificación

Taryn East
fuente
7
Eso no va a funcionar; la idcolumna no es parte del grupo, por lo que no puede referirla a menos que la agregue (por ejemplo, array_agg(id)o json_agg(id))
Craig Ringer
9

Si necesita los modelos completos, intente lo siguiente (según la respuesta de @ newUserNameHere).

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

Esto devolverá las filas donde la dirección de correo electrónico de la fila no es única.

No conozco una forma de hacer esto con múltiples atributos.

Ben Aubin
fuente
`` `User.where (email: User.select (: email) .group (: email) .having (" count (*)> 1 "))` ``
chet corey
Gracias que funciona muy bien :) También parece que el último .select(:email)es redundante. Creo que esto es un poco más limpio, pero podría estar equivocado. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
chet corey
2

Obtenga todos los duplicados con una sola consulta si usa 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
fuente
-1

Según la respuesta anterior de @newUserNameHere, creo que la forma correcta de mostrar el recuento de cada uno es

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

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