Cómo encontrar registros duplicados en PostgreSQL

190

Tengo una tabla de base de datos PostgreSQL llamada "user_links" que actualmente permite los siguientes campos duplicados:

year, user_id, sid, cid

La única limitación es actualmente el primer campo llamado "id", sin embargo ahora estoy buscando añadir una limitación para asegurarse de que el year, user_id, sidy cidson únicas, pero no puedo aplicar la restricción porque los valores duplicados ya existen, que violen esta restricción.

¿Hay alguna manera de encontrar todos los duplicados?

Juan
fuente
2
posible duplicado de Buscar filas duplicadas con PostgreSQL
drs

Respuestas:

335

La idea básica será usar una consulta anidada con agregación de conteo:

select * from yourTable ou
where (select count(*) from yourTable inr
where inr.sid = ou.sid) > 1

Puede ajustar la cláusula where en la consulta interna para limitar la búsqueda.


Hay otra buena solución para eso mencionada en los comentarios (pero no todos los leen):

select Column1, Column2, count(*)
from yourTable
group by Column1, Column2
HAVING count(*) > 1

O más corto:

SELECT (yourTable.*)::text, count(*)
FROM yourTable
GROUP BY yourTable.*
HAVING count(*) > 1
Marcin Zablocki
fuente
65
También puedes usar HAVING:select co1, col2, count(*) from tbl group by col1, col2 HAVING count(*)>1
alexkovelsky
1
Gracias @alexkovelsky, la declaración de tener fue más fácil de modificar para mí y corrió más rápido. Sugeriría una respuesta para una mayor visibilidad.
Vesanto
estas opciones me funcionaron, las otras agruparon los resultados, y estas opciones me dieron todos los registros duplicados en lugar de solo el registro duplicado, ¡gracias!
rome3ro
1
Tengo tu respuesta para que sea un poco lenta. En una tabla de 10k filas * 18 columnas, la consulta tomó 8 segundos
aydow
1
esa es la mermelada allí hermano. diablos sí. Gracias. 💯
dps
91

De " Buscar filas duplicadas con PostgreSQL " aquí hay una solución inteligente:

select * from (
  SELECT id,
  ROW_NUMBER() OVER(PARTITION BY column1, column2 ORDER BY id asc) AS Row
  FROM tbl
) dups
where 
dups.Row > 1
alexkovelsky
fuente
11
¡Esto es rápido! Trabajó en millones de filas en una fracción de segundo. Otras respuestas simplemente colgadas allí ...
dmvianna
55
Tan rápido como veo, esta consulta no considera todas las filas dentro de un grupo. Solo muestra duplicados de algo, parte de los duplicados estará con rownum = 1. Corrígeme si me equivoco
Vladimir Filipchenko
9
@vladimir Filipchenko Para tenerlo con todas las líneas, agregue un nivel a la solución Alexkovelsky:SELECT * FROM ( SELECT *, LEAD(row,1) OVER () AS nextrow FROM ( SELECT *, ROW_NUMBER() OVER(w) AS row FROM tbl WINDOW w AS (PARTITION BY col1, col2 ORDER BY col3) ) x ) y WHERE row > 1 OR nextrow > 1;
Le Droid
44
@VladimirFilipchenko Simplemente reemplace ROW_NUMBER()con COUNT(*)y agregue rows between unbounded preceding and unbounded followingdespuésORDER BY id asc
alexkovelsky
2
mucho mejor que otras soluciones que he encontrado. también funciona igualmente bien para eliminar engaños DELETE ...USINGy algunos ajustes menores
Brandon
6

Puede unirse a la misma tabla en los campos que se duplicarían y luego unirse en el campo id. Seleccione el campo id del primer alias de la tabla (tn1) y luego use la función array_agg en el campo id del segundo alias de la tabla. Finalmente, para que la función array_agg funcione correctamente, agrupará los resultados por el campo tn1.id. Esto producirá un conjunto de resultados que contiene la identificación de un registro y una matriz de todas las identificaciones que se ajustan a las condiciones de unión.

select tn1.id,
       array_agg(tn2.id) as duplicate_entries, 
from table_name tn1 join table_name tn2 on 
    tn1.year = tn2.year 
    and tn1.sid = tn2.sid 
    and tn1.user_id = tn2.user_id 
    and tn1.cid = tn2.cid
    and tn1.id <> tn2.id
group by tn1.id;

Obviamente, los id que estarán en la matriz duplicate_entries para un id, también tendrán sus propias entradas en el conjunto de resultados. Tendrá que usar este conjunto de resultados para decidir qué identificación desea que se convierta en la fuente de la 'verdad'. El único registro que no debería eliminarse. Tal vez podrías hacer algo como esto:

with dupe_set as (
select tn1.id,
       array_agg(tn2.id) as duplicate_entries, 
from table_name tn1 join table_name tn2 on 
    tn1.year = tn2.year 
    and tn1.sid = tn2.sid 
    and tn1.user_id = tn2.user_id 
    and tn1.cid = tn2.cid
    and tn1.id <> tn2.id
group by tn1.id
order by tn1.id asc)
select ds.id from dupe_set ds where not exists 
 (select de from unnest(ds.duplicate_entries) as de where de < ds.id)

Selecciona las ID de números más bajos que tienen duplicados (suponiendo que la ID está aumentando int PK). Estas serían las identificaciones que conservaría.

pwnyexpress
fuente
3

Para facilitarlo, supongo que desea aplicar una restricción única solo para el año de la columna y la clave principal es una columna llamada id.

Para encontrar valores duplicados que debe ejecutar,

SELECT year, COUNT(id)
FROM YOUR_TABLE
GROUP BY year
HAVING COUNT(id) > 1
ORDER BY COUNT(id);

Usando la instrucción sql anterior, obtienes una tabla que contiene todos los años duplicados en tu tabla. Para eliminar todos los duplicados, excepto la última entrada duplicada , debe usar la instrucción sql anterior.

DELETE
FROM YOUR_TABLE A USING YOUR_TABLE_AGAIN B
WHERE A.year=B.year AND A.id<B.id;
George Siggouroglou
fuente