92 votes

Comment supprimer les entrées dupliquées ?

Je dois ajouter une contrainte unique à une table existante. Tout va bien, sauf que la table compte déjà des millions de lignes et que nombre 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 son exécution prend une éternité. Existe-t-il un autre moyen de résoudre ce problème ? Peut-être en sauvegardant la table, puis en la restaurant après l'ajout de la contrainte ?

176voto

Tim Points 601

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

Tableau donné table Je veux le rendre unique sur (field1, field2) en gardant la ligne avec le maximum field3 :

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 un tableau, user_accounts et je veux ajouter une contrainte unique sur l'email, mais j'ai quelques doublons. Disons aussi que je veux garder le plus récemment créé (id max parmi les doublons).

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

4 votes

Cette deuxième approche est très rapide sur postgres ! Merci.

5 votes

@Tim pouvez-vous mieux expliquer ce que fait USING dans postgresql ?

3 votes

C'est de loin la meilleure réponse. Même si vous ne disposez pas d'une colonne série dans votre table pour la comparaison des identifiants, il vaut la peine d'en ajouter une temporairement pour utiliser cette approche simple.

102voto

just somebody Points 9534

Par exemple, vous pourriez :

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

2 votes

Pouvez-vous le rendre distinct pour un groupe de colonnes ? Peut-être "SELECT DISTINCT (t.a, t.b, t.c), * FROM t" ?

10 votes

37 votes

Plus facile à taper : CREATE TABLE tmp AS SELECT ...; . Ensuite, vous n'avez même pas besoin de savoir quelle est la disposition de tmp est. :)

27voto

Erwin Brandstetter Points 110228

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. Tout faire en une seule transaction .

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

Vous avez mentionné des millions de rangs. Pour que l'opération rapide vous voulez allouer suffisamment tampons temporaires pour la session. Le réglage doit être ajusté antes de tout tampon temporaire est utilisé dans votre session actuelle. Déterminez la taille de votre table :

SELECT pg_size_pretty(pg_relation_size('tbl'));

Définir temp_buffers au moins un peu plus que ça.

SET temp_buffers = 200MB;   -- example value

BEGIN;

CREATE TEMP TABLE t_tmp AS  -- retains temp for duration of session
SELECT DISTINCT * FROM tbl  -- DISTINCT folds duplicates
ORDER  BY id;               -- optionally "cluster" data

TRUNCATE tbl;

INSERT INTO tbl
SELECT * FROM t_tmp;        -- retains order (implementation detail)

COMMIT;

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

Pour les grandes tables, il est régulièrement plus rapide pour supprimer les index et les clés étrangères (FK), remplir à nouveau la table et recréer ces objets. En ce qui concerne les contraintes FK, vous devez être certain que les nouvelles données sont valides, bien sûr, ou vous rencontrerez des exceptions en essayant de créer la FK.

Notez que TRUNCATE nécessite un verrouillage plus agressif que DELETE . Cela peut poser un problème pour les tables soumises à une charge importante et simultanée. Mais c'est toujours moins perturbant que d'abandonner et de remplacer complètement la table.

Si TRUNCATE n'est pas une option ou généralement pour tables petites à moyennes il existe une technique similaire avec un CTE de modification des données (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 il peut être plus rapide (et plus simple !) pour les petites tables.

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

Pour les très grandes tables qui ne rentreraient pas dans RAM disponible en créant un nouveau sera considérablement plus rapide. Vous devrez peser le pour et le contre par rapport à d'éventuels problèmes ou frais généraux liés à des objets dépendants.

2 votes

J'ai également utilisé cette approche. Cependant, c'est peut-être personnel, mais ma table temporaire a été supprimée, et n'était plus disponible après le truncate... Faites attention à faire ces étapes si la table temporaire a été créée avec succès et est disponible.

0 votes

@xlash : Vous pouvez vérifier l'existence pour être sûr, et soit utiliser un nom différent pour la table temporaire ou réutiliser celle qui existe J'ai ajouté un peu à ma réponse.

0 votes

AVERTISSEMENT : Attention +1 à @xlash -- Je dois réimporter mes données car la table temporaire était inexistante après TRUNCATE . Comme Erwin l'a dit, assurez-vous qu'il existe avant de tronquer votre table. Voir la réponse de @codebykat

20voto

Jan Marek Points 1364

Vous pouvez utiliser l'oid ou le 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);

4 votes

Pour la suppression en place , NOT EXISTS devrait être considérablement plus rapide : DELETE FROM tbl t WHERE EXISTS (SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid > t.ctid) -- ou utilisez toute autre colonne ou ensemble de colonnes pour le tri afin de choisir un survivant.

0 votes

@ErwinBrandstetter, la requête que vous fournissez est-elle censée utiliser NOT EXISTS ?

1 votes

@John : Il doit être EXISTS ici. Lisez-le comme ceci : "Supprimez toutes les lignes où il existe une autre ligne avec la même valeur en dist_col mais un plus grand ctid ". Le seul survivant par groupe de dupes sera celui qui a le plus gros ctid .

19voto

shekwi Points 61

La fonction fenêtre de PostgreSQL est pratique pour 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);

Ver _Suppression des doublons_ .

0 votes

Et en utilisant "ctid" au lieu de "id", cela fonctionne en fait pour les lignes entièrement dupliquées.

0 votes

Excellente solution. J'ai dû faire cela pour une table contenant un milliard d'enregistrements. J'ai ajouté un WHERE au SELECT interne pour le faire par morceaux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X