web-dev-qa-db-fra.com

Comment supprimer les entrées en double?

Je dois ajouter une contrainte unique à une table existante. C'est bien, sauf que la table a déjà des millions de lignes et que beaucoup d'entre elles violent la contrainte unique que je dois ajouter.

Quelle est l’approche la plus rapide pour supprimer les lignes incriminées? J'ai une instruction SQL qui trouve les doublons et les supprime, mais cela prend une éternité pour s'exécuter. Y a-t-il un autre moyen de résoudre ce problème? Peut-être sauvegarder la table, puis restaurer après l'ajout de la contrainte?

93
gjrwebber

Par exemple, vous pourriez:

CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
101
just somebody

Certaines de ces approches semblent un peu compliquées, et je le fais généralement comme:

Étant donné la table table, souhaitez l’unifier sur (champ1, champ2) en conservant la ligne avec le champ max3:

DELETE FROM table USING table alias 
  WHERE table.field1 = alias.field1 AND table.field2 = alias.field2 AND
    table.max_field < alias.max_field

Par exemple, j'ai une table, user_accounts, et je veux ajouter une contrainte unique au courrier électronique, mais j'ai des doublons. Dites aussi que je veux conserver le dernier créé (max id parmi les doublons).

DELETE FROM user_accounts USING user_accounts ua2
  WHERE user_accounts.email = ua2.email AND user_account.id < ua2.id;
  • Remarque - USING n'est pas un langage SQL standard, c'est une extension PostgreSQL (mais une très utile), mais la question d'origine mentionne spécifiquement PostgreSQL.
173
Tim

Au lieu de créer une nouvelle table, vous pouvez également réinsérer des lignes uniques dans la même table après l'avoir tronquée. Faites tout en une seule transaction . Vous pouvez éventuellement supprimer la table temporaire à la fin de la transaction automatiquement avec ON COMMIT DROP. Voir ci-dessous.

Cette approche n'est utile que lorsqu'il y a beaucoup de lignes à supprimer de la table. Pour quelques doublons, utilisez un simple DELETE.

Vous avez mentionné des millions de lignes. Pour que l'opération soit rapide , vous souhaitez allouer suffisamment de mémoires tampons temporaires pour la session. Le réglage doit être ajusté avant tout tampon temporaire est utilisé dans votre session en cours. Découvrez la taille de votre table:

SELECT pg_size_pretty(pg_relation_size('tbl'));

Ensemble temp_buffers en conséquence. Arrondissez généreusement car la représentation en mémoire nécessite un peu plus de RAM.

SET temp_buffers = 200MB;    -- example value

BEGIN;

-- CREATE TEMPORARY TABLE t_tmp ON COMMIT DROP AS -- drop temp table at commit
CREATE TEMPORARY TABLE t_tmp AS  -- retain temp table after commit
SELECT DISTINCT * FROM tbl;  -- DISTINCT folds duplicates

TRUNCATE tbl;

INSERT INTO tbl
SELECT * FROM t_tmp;
-- ORDER BY id; -- optionally "cluster" data while being at it.

COMMIT;

Cette méthode peut être supérieure à la création d'une nouvelle table si des objets dépendants existent. Vues, ​​index, clés étrangères ou autres objets faisant référence à la table. TRUNCATE vous oblige de toute façon à commencer avec une ardoise vierge (nouveau fichier en arrière-plan) et est beaucoup plus rapide que DELETE FROM tbl avec de grandes tables (DELETE peut être plus rapide avec de petites tables).

Pour les grandes tables, il est régulièrement plus rapide de supprimer les index et les clés étrangères, de remplir la table et de recréer ces objets. En ce qui concerne les contraintes de fk, vous devez bien sûr être certain que les nouvelles données sont valables, sinon vous rencontrerez une exception si vous essayez de créer le fk.

Notez que TRUNCATE requiert un verrouillage plus agressif que DELETE. Cela peut poser un problème pour les tables à forte charge simultanée.

Si TRUNCATE n’est pas une option ou généralement pour les tables petites à moyennes , il existe une technique similaire avec un modification des données). CTE (Postgres 9.1 +):

WITH del AS (DELETE FROM tbl RETURNING *)
INSERT INTO tbl
SELECT DISTINCT * FROM del;
-- ORDER BY id; -- optionally "cluster" data while being at it.

Plus lent pour les grandes tables, car TRUNCATE est plus rapide là-bas. Mais peut être plus rapide (et plus simple!) Pour les petites tables.

Si vous n'avez aucun objet dépendant du tout, vous pouvez créer une nouvelle table et supprimer l'ancienne, mais vous ne gagnerez presque rien par rapport à cette approche universelle.

Pour les très grandes tables qui ne rentrent pas dans la mémoire vive disponible , la création d'une table nouvelle sera considérablement plus rapide. Vous devrez peser cela par rapport à d'éventuels problèmes/frais généraux avec des objets dépendants.

25
Erwin Brandstetter

Vous pouvez utiliser oid ou ctid, qui est normalement une colonne "non visible" dans la table:

DELETE FROM table
 WHERE ctid NOT IN
  (SELECT MAX(s.ctid)
    FROM table s
    GROUP BY s.column_has_be_distinct);
20
Jan Marek

La fonction de fenêtre PostgreSQL est pratique pour résoudre ce problème.

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);

Voir Supprimer les doublons.

19
shekwi

Requête généralisée pour supprimer les doublons:

DELETE FROM table_name
WHERE ctid NOT IN (
  SELECT max(ctid) FROM table_name
  GROUP BY column1, [column 2, ...]
);

La colonne ctid est une colonne spéciale disponible pour chaque table, mais non visible sauf mention contraire. La valeur de la colonne ctid est considérée comme unique pour chaque ligne d'une table.

8
naXa

De ne ancienne liste de diffusion postgresql.org :

create table test ( a text, b text );

Valeurs uniques

insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );

Dupliquer les valeurs

insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );

Une double copie en plus

insert into test values ( 'x', 'y');

select oid, a, b from test;

Sélectionnez les lignes en double

select o.oid, o.a, o.b from test o
    where exists ( select 'x'
                   from test i
                   where     i.a = o.a
                         and i.b = o.b
                         and i.oid < o.oid
                 );

Supprimer les lignes en double

Remarque: PostgreSQL ne supporte pas les alias sur la table mentionnée dans la clause from d’une suppression.

delete from test
    where exists ( select 'x'
                   from test i
                   where     i.a = test.a
                         and i.b = test.b
                         and i.oid < test.oid
             );
7
Bhavik Ambani

Je viens d'utiliser réponse d'Erwin Brandstetter avec succès pour supprimer les doublons dans une table de jointure (une table dépourvue de ses propres ID principaux), mais j'ai constaté qu'il y avait une mise en garde importante.

Comprenant ON COMMIT DROP _ signifie que la table temporaire sera supprimée à la fin de la transaction. Pour moi, cela signifiait que la table temporaire n'était plus disponible au moment où je suis allée l'insérer!

Je viens de faire CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl; et tout a bien fonctionné.

La table temporaire est supprimée à la fin de la session.

4
codebykat

Tout d’abord, vous devez décider lequel de vos "doublons" vous allez conserver. Si toutes les colonnes sont égales, vous pouvez supprimer l’une d’entre elles ... Mais vous voulez peut-être ne conserver que le critère le plus récent ou un autre?

Le moyen le plus rapide dépend de votre réponse à la question ci-dessus, ainsi que du% de doublons sur la table. Si vous jetez 50% de vos lignes, vous feriez mieux de faire CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;, et si vous supprimez 1% des lignes, utiliser DELETE est préférable.

Également pour les opérations de maintenance comme celle-ci, il est généralement bon de définir work_mem sur une bonne partie de votre RAM: lancez EXPLAIN, vérifiez le nombre N de tris/hachages, et définissez work_mem sur votre RAM/2/N. Utilisez beaucoup de RAM; c'est bon pour tant que vous n’avez qu’une connexion simultanée ...

3
peufeu
DELETE FROM table
  WHERE something NOT IN
    (SELECT     MAX(s.something)
      FROM      table As s
      GROUP BY  s.this_thing, s.that_thing);
3
Secko

Cette fonction supprime les doublons sans supprimer les index et le fait dans n'importe quelle table.

Utilisation: select remove_duplicates('mytable');

 --- 
 --- remove_duplicates (nom_table) supprime les enregistrements en double d'une table (conversion d'un jeu unique) 
 --- 
 CREATE OR REPLACE FUNCTION remove_duplicates (text) RETURNS void AS $$ 
 DECLARE 
 Nom_table ALIAS FOR $ 1; 
 COMMENCER 
 EXÉCUTER 'CRÉER UNE TABLE TEMPORAIRE _DISTINCT_' || nom_table || 'AS (SELECT DISTINCT * FROM' || nom_table || ');'; 
 EXECUTE 'DELETE FROM' || nom_table || ';'; 
 EXECUTE 'INSERT INTO' || nom_table || '(SELECT * FROM _DISTINCT_' || tablename || ');'; 
 EXECUTE 'DROP TABLE _DISTINCT_' || nom_table || ';'; 
 RETURN; 
 END; 
 $$ LANGUAGE plpgsql; 
3
Ole Tange

Si vous avez seulement une ou quelques entrées dupliquées et qu'elles sont bien dupliquées (c'est-à-dire qu'elles apparaissent deux fois), vous pouvez utiliser la colonne "hidden" ctid, comme proposé ci-dessus. , avec LIMIT:

DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);

Cela ne supprimera que la première des lignes sélectionnées.

3

Cela fonctionne très bien et est très rapide:

CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
1
Mark Cupitt
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);

Supprimer les doublons par colonne (s) et conserver la ligne avec l'id le plus bas. Le motif est pris dans le postgres wiki

En utilisant les CTE, vous pouvez obtenir une version plus lisible de ce qui précède grâce à cette

WITH duplicate_ids as (
    SELECT id, rnum 
    FROM num_of_rows
    WHERE rnum > 1
),
num_of_rows as (
    SELECT id, 
        ROW_NUMBER() over (partition BY column1, 
                                        column2, 
                                        column3 ORDER BY id) AS rnum
        FROM tablename
)
DELETE FROM tablename 
WHERE id IN (SELECT id from duplicate_ids)
1
denplis
CREATE TABLE test (col text);
INSERT INTO test VALUES
 ('1'),
 ('2'), ('2'),
 ('3'),
 ('4'), ('4'),
 ('5'),
 ('6'), ('6');
DELETE FROM test
 WHERE ctid in (
   SELECT t.ctid FROM (
     SELECT row_number() over (
               partition BY col
               ORDER BY col
               ) AS rnum,
            ctid FROM test
       ORDER BY col
     ) t
    WHERE t.rnum >1);
1
Shamseer PC

Je travaille avec PostgreSQL 8.4. Lorsque j'ai exécuté le code proposé, j'ai constaté qu'il ne supprimait pas les doublons. Lors de l'exécution de certains tests, j'ai constaté que l'ajout de "DISTINCT ON (duplicate_column_name)" et de "ORDER BY duplicate_column_name" faisait l'affaire. Je ne suis pas un gourou du langage SQL, je l’ai trouvé dans la doc SELECT ... DISTINCT PostgreSQL 8.4.

CREATE OR REPLACE FUNCTION remove_duplicates(text, text) RETURNS void AS $$
DECLARE
  tablename ALIAS FOR $1;
  duplicate_column ALIAS FOR $2;
BEGIN
  EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT ON (' || duplicate_column || ') * FROM ' || tablename || ' ORDER BY ' || duplicate_column || ' ASC);';
  EXECUTE 'DELETE FROM ' || tablename || ';';
  EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');';
  EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';';
  RETURN;
END;
$$ LANGUAGE plpgsql;
1
CM.