Come trovare record duplicati in PostgreSQL


190

Ho una tabella di database PostgreSQL chiamata "link_utente" che attualmente consente i seguenti campi duplicati:

year, user_id, sid, cid

Il vincolo univoco è attualmente il primo campo chiamato "id", però ora sto cercando di aggiungere un vincolo per assicurarsi che il year, user_id, side cidsono tutti unici, ma non posso applicare il vincolo perché i valori duplicati esistono già che violano questo vincolo.

C'è un modo per trovare tutti i duplicati?


2
possibile duplicato di Trova righe duplicate con PostgreSQL
drs

Risposte:


335

L'idea di base utilizzerà una query nidificata con aggregazione dei conteggi:

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

È possibile regolare la clausola where nella query interna per restringere la ricerca.


C'è un'altra buona soluzione per quella menzionata nei commenti, (ma non tutti li leggono):

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

O più breve:

SELECT (yourTable.*)::text, count(*)
FROM yourTable
GROUP BY yourTable.*
HAVING count(*) > 1

65
Puoi anche usare HAVING:select co1, col2, count(*) from tbl group by col1, col2 HAVING count(*)>1
alexkovelsky il

1
Grazie a @alexkovelsky la dichiarazione di avere è stata più facile da modificare per me e ha funzionato più velocemente. Suggerirei una risposta con essa per una maggiore visibilità.
Vesanto

queste opzioni mi hanno funzionato, le altre raggruppano i risultati e queste opzioni mi hanno dato tutti i record duplicati anziché solo i record duplicati, grazie!
roma3ro,

1
Ho questa tua risposta per essere un po 'lento. Su una tabella 10k righe * 18 colonne, la query ha richiesto 8 secondi
tra il

1
questa è la marmellata proprio lì fratello. diamine si. Grazie. 💯
dps,

91

Da " Trova righe duplicate con PostgreSQL " ecco la soluzione intelligente:

select * from (
  SELECT id,
  ROW_NUMBER() OVER(PARTITION BY column1, column2 ORDER BY id asc) AS Row
  FROM tbl
) dups
where 
dups.Row > 1

11
Questo è veloce! Ha funzionato su milioni di file in una frazione di secondo. Altre risposte sono appena appese lì ...
dmvianna

5
Per quanto vedo, questa query non considera tutte le righe all'interno di un gruppo. Mostra solo duplicati di qualcosa, parte dei duplicati sarà con rownum = 1. Correggimi se sbaglio
Vladimir Filipchenko

9
@vladimir Filipchenko Per averlo con tutte le linee, aggiungi un livello alla soluzione 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 sostituire ROW_NUMBER()con COUNT(*)e aggiungere rows between unbounded preceding and unbounded followingdopoORDER BY id asc
alexkovelsky il

2
molto meglio di altre soluzioni che ho trovato. funziona ugualmente bene anche per la cancellazione di duplicati DELETE ...USINGe alcune piccole modifiche
Brandon,

6

Puoi unirti alla stessa tabella sui campi che sarebbero duplicati e quindi anti-join sul campo ID. Selezionare il campo id dal primo alias di tabella (tn1) e quindi utilizzare la funzione array_agg sul campo id del secondo alias di tabella. Infine, affinché la funzione array_agg funzioni correttamente, raggrupperai i risultati in base al campo tn1.id. Ciò produrrà un set di risultati che contiene l'id di un record e un array di tutti gli id ​​che si adattano alle condizioni di join.

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;

Ovviamente, gli ID che saranno nella matrice duplicate_entries per un ID, avranno anche le loro voci nel set di risultati. Dovrai utilizzare questo set di risultati per decidere quale ID vuoi diventare la fonte della "verità". L'unico record che non dovrebbe essere cancellato. Forse potresti fare qualcosa del genere:

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)

Seleziona l'ID numero più basso con duplicati (supponendo che l'ID stia aumentando in PK). Questi sarebbero gli ID che dovresti tenere in giro.


3

Per semplificare, suppongo che si desideri applicare un vincolo univoco solo per l'anno della colonna e la chiave primaria è una colonna denominata id.

Per trovare valori duplicati che dovresti eseguire,

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

Utilizzando l'istruzione sql sopra si ottiene una tabella che contiene tutti gli anni duplicati nella tabella. Per eliminare tutti i duplicati tranne l'ultima voce duplicata, è necessario utilizzare l'istruzione sql sopra.

DELETE
FROM YOUR_TABLE A USING YOUR_TABLE_AGAIN B
WHERE A.year=B.year AND A.id<B.id;
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.