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


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


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


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.


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

-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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.