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 ?

Était-ce utile?

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);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top