Como encontrar registros duplicados no PostgreSQL

190

Eu tenho uma tabela de banco de dados PostgreSQL chamada "user_links" que atualmente permite os seguintes campos duplicados:

year, user_id, sid, cid

A única restrição é atualmente o primeiro campo chamado "id", porém agora estou olhando para adicionar uma restrição para garantir que o year, user_id, side cidsão todos originais, mas não posso aplicar a restrição, porque valores duplicados já existem que violar esta restrição.

Existe uma maneira de encontrar todas as duplicatas?

John
fonte
2
possível duplicata Encontrar linhas duplicadas com PostgreSQL
DRS

Respostas:

335

A ideia básica será usar uma consulta aninhada com agregação de contagem:

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

Você pode ajustar a cláusula where na consulta interna para restringir a pesquisa.


Há outra boa solução para a mencionada nos comentários (mas nem todos os lêem):

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

Ou mais curto:

SELECT (yourTable.*)::text, count(*)
FROM yourTable
GROUP BY yourTable.*
HAVING count(*) > 1
Marcin Zablocki
fonte
65
Você também pode usar HAVING:select co1, col2, count(*) from tbl group by col1, col2 HAVING count(*)>1
alexkovelsky
1
Obrigado @alexkovelsky, a declaração having foi mais fácil de modificar e correu mais rapidamente. Eu sugeriria uma resposta para obter maior visibilidade.
Vesanto 14/03
essas opções funcionaram para mim, os outros agrupam os resultados e essas opções me deram todos os registros duplicados em vez de apenas o registro duplicado, obrigado!
Rome3ro 12/07
1
Eu tenho sua resposta para ser um pouco lenta. Em uma tabela de 10k linhas * 18 colunas, a consulta levou 8 segundos
aydow 25/10
1
isso é a geléia ali, mano. Parreira sim. obrigado. 💯
dps
91

Em " Encontre linhas duplicadas com o PostgreSQL ", aqui está a solução 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
fonte
11
Isso é rápido! Trabalhou mais de milhões de linhas em uma fração de segundo. Outras respostas simplesmente foram penduradas lá ...
dmvianna 4/16/16
5
Pelo que sei, esta consulta não considera todas as linhas de um grupo. Ele mostra apenas duplica a algo, parte das duplicatas será com rownum = 1. me corrija se eu errado
Vladimir Filipchenko
9
@vladimir Filipchenko Para tê-lo com todas as linhas, adicione um nível à solução 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
4
@VladimirFilipchenko Basta substituir ROW_NUMBER()por COUNT(*)e adicionar rows between unbounded preceding and unbounded followingdepois #ORDER BY id asc
alexkovelsky 15/17/17 /
2
muito melhor do que outras soluções que encontrei. também funciona igualmente bem para apagar dupes com DELETE ...USINGe alguns pequenos ajustes
Brandon
6

Você pode ingressar na mesma tabela nos campos que seriam duplicados e depois ingressar no campo de identificação. Selecione o campo de identificação no primeiro alias da tabela (tn1) e use a função array_agg no campo de identificação do segundo alias da tabela. Por fim, para que a função array_agg funcione corretamente, você agrupará os resultados pelo campo tn1.id. Isso produzirá um conjunto de resultados que contém o ID de um registro e uma matriz de todos os IDs que atendem às condições de junção.

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, os IDs que estarão na matriz duplicate_entries para um ID também terão suas próprias entradas no conjunto de resultados. Você precisará usar esse conjunto de resultados para decidir qual ID deseja tornar a fonte de 'verdade'. O único registro que não deve ser excluído. Talvez você possa fazer algo assim:

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)

Seleciona os IDs de número mais baixo duplicados (assumindo que o ID esteja aumentando em PK). Esses seriam os IDs que você manteria por perto.

pwnyexpress
fonte
3

Para facilitar, presumo que você deseja aplicar uma restrição exclusiva apenas para o ano da coluna e a chave primária é uma coluna chamada id.

Para encontrar valores duplicados, você deve executar,

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

Usando a instrução sql acima, você obtém uma tabela que contém todos os anos duplicados em sua tabela. Para excluir todas as duplicatas, exceto a última entrada duplicada, você deve usar a instrução sql acima.

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