Elimina i record duplicati in PostgreSQL


113

Ho una tabella in un database PostgreSQL 8.3.8, che non ha chiavi / vincoli su di esso e ha più righe con esattamente gli stessi valori.

Vorrei rimuovere tutti i duplicati e conservare solo 1 copia di ogni riga.

C'è una colonna in particolare (chiamata "chiave") che può essere usata per identificare i duplicati (cioè dovrebbe esistere solo una voce per ogni "chiave" distinta).

Come posso fare questo? (idealmente con un singolo comando SQL) La velocità non è un problema in questo caso (ci sono solo poche righe).

Risposte:


80
DELETE FROM dupes a
WHERE a.ctid <> (SELECT min(b.ctid)
                 FROM   dupes b
                 WHERE  a.key = b.key);

20
Non usarlo, è troppo lento!
Paweł Malisak

5
Sebbene questa soluzione funzioni sicuramente, la soluzione di @rapimo di seguito viene eseguita molto più velocemente. Credo che questo abbia a che fare con l'istruzione select interna qui eseguita N volte (per tutte le N righe nella tabella dupes) piuttosto che con il raggruppamento in corso nell'altra soluzione.
David

Per tabelle enormi (diversi milioni di record), questo si adatta effettivamente alla memoria, a differenza della soluzione di @ rapimo. Quindi in quei casi questo è quello più veloce (nessuno scambio).
Giel

1
Aggiunta spiegazione: funziona perché ctid è una colonna postgres speciale che indica la posizione fisica della riga. Puoi usarlo come ID univoco anche se la tua tabella non possiede un ID univoco. postgresql.org/docs/8.2/ddl-system-columns.html
Eric Burel

194

Una soluzione più veloce è

DELETE FROM dups a USING (
      SELECT MIN(ctid) as ctid, key
        FROM dups 
        GROUP BY key HAVING COUNT(*) > 1
      ) b
      WHERE a.key = b.key 
      AND a.ctid <> b.ctid

20
Perché è più veloce della soluzione a_horse_with_no_name?
Roberto

3
Questo è più veloce perché esegue solo 2 query. Prima uno per selezionare tutti i duplicati, poi uno per eliminare tutti gli elementi dalla tabella. La query di @a_horse_with_no_name esegue una query per vedere se corrisponde a qualsiasi altro per ogni singolo elemento nella tabella.
Aeolun

5
che cos'è ctid?
techkuz

6
da documenti: ctid. La posizione fisica della versione riga all'interno della relativa tabella. Notare che sebbene il ctid possa essere utilizzato per individuare la versione della riga molto rapidamente, il ctid di una riga cambierà ogni volta che viene aggiornato o spostato da VACUUM FULL. Pertanto ctid è inutile come identificatore di riga a lungo termine.
Saim

1
Sembra che questo non funzioni quando si hanno più di 2 righe duplicate, perché elimina solo un duplicato alla volta.
Frankie Drake

73

Questo è veloce e conciso:

DELETE FROM dupes T1
    USING   dupes T2
WHERE   T1.ctid < T2.ctid  -- delete the older versions
    AND T1.key  = T2.key;  -- add more columns if needed

Vedi anche la mia risposta su Come eliminare le righe duplicate senza identificatore univoco che include ulteriori informazioni.


cosa significa ct? contare?
techkuz

4
@trthhrtz ctidpunta alla posizione fisica del record nella tabella. Contrariamente a quanto ho scritto al momento nel commento, l'uso dell'operatore minore di non indica necessariamente la versione precedente in quanto il ct può andare a capo e un valore con un ctid inferiore potrebbe effettivamente essere più recente.
isapir

1
Per tua informazione, ho provato questa soluzione e l'ho interrotta dopo aver atteso 15 minuti. Ho provato la soluzione di rapimo ed è stata completata in circa 10 secondi (eliminate ~ 700.000 righe).
Patrick

@Patrick non può immaginare se il tuo db non ha un identificatore univoco poiché la risposta di rapimo non funziona in quel caso.
stucash

@isapir Sono solo curioso, le risposte sopra, stanno mantenendo i record più vecchi esattamente come hanno selezionato min(ctid)? mentre i tuoi mantengono quelli più nuovi? Grazie!
stucash

17

Ho provato questo:

DELETE FROM tablename
WHERE id IN (SELECT id
              FROM (SELECT id,
                             ROW_NUMBER() OVER (partition BY column1, column2, column3 ORDER BY id) AS rnum
                     FROM tablename) t
              WHERE t.rnum > 1);

fornito da Postgres wiki:

https://wiki.postgresql.org/wiki/Deleting_duplicates


Qualche idea della performance rispetto alla risposta di @ rapimo e quella accettata (@a_horse_with_no_name)?
tuxayo

3
Questo non funzionerà se, come afferma la domanda, tutte le colonne sono identiche, idinclusa.
ibizaman

Questa query eliminerà sia la copia originale che i duplicati. la domanda riguarda il mantenimento di almeno una riga.
pyBomb

@pyBomb sbagliato, manterrà il primo in idcui le colonne 1 ... 3 sono duplicate
Jeff

A partire da postgresql 12, questa è di gran lunga la soluzione più veloce (contro 300 milioni di righe). Ho appena testato tutto ciò che viene proposto in questa domanda, inclusa la risposta accettata, e questa soluzione "ufficiale" è effettivamente la più veloce e soddisfa tutti i requisiti di OP (e il mio)
Jeff

7

Ho dovuto creare la mia versione. La versione scritta da @a_horse_with_no_name è troppo lenta sulla mia tabella (21 milioni di righe). E @rapimo semplicemente non elimina i duplicati.

Ecco cosa utilizzo su PostgreSQL 9.5

DELETE FROM your_table
WHERE ctid IN (
  SELECT unnest(array_remove(all_ctids, actid))
  FROM (
         SELECT
           min(b.ctid)     AS actid,
           array_agg(ctid) AS all_ctids
         FROM your_table b
         GROUP BY key1, key2, key3, key4
         HAVING count(*) > 1) c);

6

Userei una tabella temporanea:

create table tab_temp as
select distinct f1, f2, f3, fn
  from tab;

Quindi, elimina tabe rinomina tab_tempin tab.


8
Questo approccio non tiene conto di trigger, indici e statistiche. Certamente potresti aggiungerli, ma aggiunge anche molto più lavoro.
Jordan

Non tutti ne hanno bisogno. Questo approccio è estremamente veloce e ha funzionato molto meglio del resto su 200k email (varchar 250) senza indici.
Sergey Telshevsky

Codice completo:DROP TABLE IF EXISTS tmp; CREATE TABLE tmp as ( SELECT * from (SELECT DISTINCT * FROM your_table) as t ); DELETE from your_table; INSERT INTO your_table SELECT * from tmp; DROP TABLE tmp;
Eric Burel

1

Un altro approccio (funziona solo se hai un campo univoco come idnella tua tabella) per trovare tutti gli ID univoci per colonne e rimuovere altri ID che non sono nell'elenco univoco

DELETE
FROM users
WHERE users.id NOT IN (SELECT DISTINCT ON (username, email) id FROM users);

Il fatto è che nella mia domanda le tabelle non avevano ID univoci; i "duplicati" erano più righe con esattamente gli stessi valori su tutte le colonne.
André Morujão

Bene, ho aggiunto alcune note
Zaytsev Dmitry

1

Che ne dite di:

CON
  u AS (SELECT DISTINCT * FROM your_table),
  x AS (DELETE FROM your_table)
INSERISCI NELLA tua_tabella SELEZIONA * DA u;

Ero preoccupato per l'ordine di esecuzione, l'eliminazione sarebbe avvenuta prima di SELECT DISTINCT, ma per me funziona bene. E ha il vantaggio di non aver bisogno di alcuna conoscenza della struttura del tavolo.


L'unico svantaggio è che se hai un tipo di dati che non supporta l'uguaglianza (ad esempio json), questo non funzionerà.
a_horse_with_no_name

0

Questo ha funzionato bene per me. Avevo una tabella, termini, che conteneva valori duplicati. Ha eseguito una query per popolare una tabella temporanea con tutte le righe duplicate. Quindi ho eseguito l'istruzione a delete con quegli ID nella tabella temporanea. valore è la colonna che conteneva i duplicati.

        CREATE TEMP TABLE dupids AS
        select id from (
                    select value, id, row_number() 
over (partition by value order by value) 
    as rownum from terms
                  ) tmp
                  where rownum >= 2;

delete from [table] where id in (select id from dupids)

0

Ecco una soluzione che utilizza PARTITION BY:

DELETE FROM dups
USING (
  SELECT
    ctid,
    (ctid != min(ctid) OVER (PARTITION BY key_column1, key_column2 [...])) AS is_duplicate
  FROM dups 
) dups_find_duplicates
WHERE dups.ctid == dups_find_duplicates.ctid
AND dups_find_duplicates.is_duplicate
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.