Comment supprimer les entrées en double ?
-
20-09-2019 - |
Question
Je dois ajouter une contrainte unique à une table existante.C'est bien, sauf que la table contient déjà des millions de lignes et que de nombreuses lignes violent la contrainte d'unicité 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 sauvegarder la table, puis restaurer après l'ajout de la contrainte ?
La solution
Par exemple, vous pouvez:
CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
Autres conseils
Certaines de ces approches semblent un peu compliqué, et je le fais en général ce que:
Compte tenu de la table table
, veulent uniques sur (champ1, champ2) garder 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 une table, user_accounts
, et je veux ajouter une contrainte unique e-mail, mais j'ai quelques doublons. Dire aussi que je veux garder le plus récemment créé (id max entre 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 standard SQL, il est une extension de PostgreSQL (mais très utile une), mais la question initiale mentionne spécifiquement PostgreSQL .
Au lieu de créer un nouveau tableau, vous pouvez également réinsérer des lignes uniques dans le même tableau après l'avoir tronqué.Fait tout en une seule opération.En option, vous pouvez supprimer automatiquement la table temporaire à la fin de la transaction avec ON COMMIT DROP
.Voir ci-dessous.
Cette approche n'est utile que lorsqu'il y a de nombreuses lignes à supprimer partout dans la table.Pour quelques doublons seulement, utilisez un simple DELETE
.
Vous avez mentionné des millions de lignes.Pour faire l'opération rapide vous voulez allouer suffisamment tampons temporaires pour la séance.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
par conséquent.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 référençant la table. TRUNCATE
vous fait de toute façon commencer avec une table rase (nouveau fichier en arrière-plan) et est beaucoup plus rapide que DELETE FROM tbl
avec de grandes tables (DELETE
peut en fait être plus rapide avec de petites tables).
Pour les grandes tablées, c'est régulièrement plus rapide pour supprimer les index et les clés étrangères, remplir la table et recréer ces objets.En ce qui concerne les contraintes fk, vous devez bien sûr être certain que les nouvelles données sont valides, sinon vous rencontrerez une exception en essayant de créer le fk.
Noter que TRUNCATE
nécessite un verrouillage plus agressif que DELETE
.Cela peut être un problème pour les tables avec une charge simultanée importante.
Si TRUNCATE
n'est pas une option ou généralement pour petites et moyennes tables il existe une technique similaire avec un CTE de modification de 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
c'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, vous pouvez créer une nouvelle table et supprimer l'ancienne, mais vous ne gagnerez pratiquement rien par rapport à cette approche universelle.
Pour les très grandes tables qui ne rentreraient pas RAM disponible, créer un nouveau la table sera considérablement plus rapide.Vous devrez mettre cela en balance avec d'éventuels problèmes/surcharges avec les objets dépendants.
Vous pouvez utiliser oid ou ctid, qui est normalement une colonne « non visibles » dans le tableau:
DELETE FROM table
WHERE ctid NOT IN
(SELECT MAX(s.ctid)
FROM table s
GROUP BY s.column_has_be_distinct);
La fonction de fenêtre PostgreSQL est à portée de main 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);
Voir Suppression des doublons .
requête Généralisée à supprimer les doublons:
DELETE FROM table_name
WHERE ctid NOT IN (
SELECT max(ctid) FROM table_name
GROUP BY column1, [column 2, ...]
);
La ctid
colonne est une colonne spéciale disponible pour chaque table mais pas visible, sauf mention spécifique. La valeur de la colonne ctid
est considérée comme unique pour chaque ligne dans une table.
De une 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' );
Les valeurs en double
insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );
Encore un double à double
insert into test values ( 'x', 'y');
select oid, a, b from test;
Sélectionner les doublons
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 doublons
Note: PostgreSQL dosn't alias de support 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
);
Je viens d'utiliser réponse de Erwin Brandstetter avec succès à supprimer les doublons dans une table de jointure (une table manquant ses propres identifiants primaires), mais a constaté qu'il ya une mise en garde importante.
ON COMMIT DROP
Y compris signifie la table temporaire DROPpé à la fin de la transaction. Pour moi, cela signifiait la table temporaire était ne sont plus disponibles au moment où je suis allé à insérer!
Je viens de faire CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
et tout fonctionnait bien.
La table temporaire ne soit abandonné à la fin de la session.
Cette fonction supprime les doublons sans supprimer les index et il le fait à une table.
Utilisation: select remove_duplicates('mytable');
--- --- remove_duplicates(tablename) removes duplicate records from a table (convert from set to unique set) --- CREATE OR REPLACE FUNCTION remove_duplicates(text) RETURNS void AS $$ DECLARE tablename ALIAS FOR $1; BEGIN EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT * FROM ' || tablename || ');'; EXECUTE 'DELETE FROM ' || tablename || ';'; EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');'; EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';'; RETURN; END; $$ LANGUAGE plpgsql;
DELETE FROM table
WHERE something NOT IN
(SELECT MAX(s.something)
FROM table As s
GROUP BY s.this_thing, s.that_thing);
Si vous avez seulement un ou quelques entrées doubles et ils sont en effet dupliqué (c'est, ils apparaissent deux fois), vous pouvez utiliser la colonne ctid
« caché », tel que proposé ci-dessus, ainsi que avec LIMIT
:
DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);
Ceci supprimera uniquement la première des lignes sélectionnées.
D'abord, vous devez décider lequel de vos « doublons » vous garderez. Si toutes les colonnes sont égales, OK, vous pouvez supprimer l'un d'eux ... Mais peut-être que vous voulez garder que les plus récents, ou un autre critère?
La façon la plus rapide dépend de votre réponse à la question ci-dessus, et aussi sur le% des doublons sur la table. Si vous jetez à 50% de vos lignes, vous êtes mieux faire CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
, et si vous supprimez 1% des lignes, en utilisant la suppression est mieux.
De même pour les opérations de maintenance comme celui-ci, il est généralement bon de mettre work_mem
à une bonne partie de votre RAM: run EXPLIQUER, vérifiez le nombre N de sortes / hash, et mis work_mem à votre RAM / 2 / N. Utilisez beaucoup de RAM; il est bon pour la vitesse. Tant que vous avez une seule connexion simultanée ...
Je travaille avec PostgreSQL 8.4. Quand je courais le code proposé, je trouve qu'il n'a pas été la suppression des doublons en fait. Dans l'exécution de certains essais, j'ai trouvé que l'ajout du "DISTINCT ON (duplicate_column_name)" et "ORDER BY duplicate_column_name" a fait l'affaire. Je ne suis pas gourou SQL, j'ai trouvé dans PostgreSQL 8.4 SELECT ... DISTINCT doc.
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;
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;
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 de colonne (s) et de garder la ligne avec le plus bas id. Le modèle est tiré de la postgres
Utilisation CTEs vous pouvez obtenir une version plus lisible de ce qui précède dans ce
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)
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);