web-dev-qa-db-fra.com

Comment supprimer les lignes en double sans identifiant unique

J'ai des lignes en double dans ma table et je veux supprimer les doublons de la manière la plus efficace car la table est grande. Après quelques recherches, je suis venu avec cette requête:

WITH TempEmp AS
(
SELECT name, ROW_NUMBER() OVER(PARTITION by name, address, zipcode ORDER BY name) AS duplicateRecCount
FROM mytable
)
-- Now Delete Duplicate Records
DELETE FROM TempEmp
WHERE duplicateRecCount > 1;

Mais cela ne fonctionne que dans SQL, pas dans Netezza. Il semblerait qu’il n’aime pas la clause DELETE après la clause WITH?

34
moe

J'aime la solution de @ erwin-brandstetter, mais je voulais montrer une solution avec le mot clé USING:

DELETE   FROM table_with_dups T1
  USING       table_with_dups T2
WHERE  T1.ctid    < T2.ctid       -- delete the "older" ones
  AND  T1.name    = T2.name       -- list columns that define duplicates
  AND  T1.address = T2.address
  AND  T1.zipcode = T2.zipcode;

Si vous souhaitez examiner les enregistrements avant de les supprimer, remplacez simplement DELETE par SELECT * et USING par une virgule ,, i.e.

SELECT * FROM table_with_dups T1
  ,           table_with_dups T2
WHERE  T1.ctid    < T2.ctid       -- select the "older" ones
  AND  T1.name    = T2.name       -- list columns that define duplicates
  AND  T1.address = T2.address
  AND  T1.zipcode = T2.zipcode;

Mise à jour: j'ai testé certaines des solutions différentes ici pour la vitesse. Si vous ne vous attendez pas à beaucoup de doublons, cette solution fonctionne beaucoup mieux que celles qui ont une clause NOT IN (...) car celles-ci génèrent beaucoup de lignes dans la sous-requête.

Si vous réécrivez la requête pour qu'elle utilise IN (...), la procédure présentée est identique à celle présentée ici, mais le code SQL devient beaucoup moins concis.

Mise à jour 2: Si vous avez des valeurs NULL dans l'une des colonnes de clé (ce que vous ne devriez vraiment pas utiliser IMO), vous pouvez utiliser COALESCE() dans la condition de cette colonne, par exemple.

  AND COALESCE(T1.col_with_nulls, '[NULL]') = COALESCE(T2.col_with_nulls, '[NULL]')
25
isapir

Si vous n'avez pas d'autre identifiant unique, vous pouvez utiliser ctid:

delete from mytable
    where exists (select 1
                  from mytable t2
                  where t2.name = mytable.name and
                        t2.address = mytable.address and
                        t2.Zip = mytable.Zip and
                        t2.ctid > mytable.ctid
                 );

C'est une bonne idée d'avoir un identifiant unique, auto-incrémenté dans chaque table. Faire une delete comme ceci en est une raison importante.

48
Gordon Linoff

Dans un monde parfait, chaque table a un identifiant unique.
En l'absence de colonne unique (ou d'une combinaison de celles-ci), utilisez la colonne ctid :

DELETE FROM tbl
WHERE  ctid NOT IN (
   SELECT min(ctid)                    -- ctid is NOT NULL by definition
   FROM   tbl
   GROUP  BY name, address, zipcode);  -- list columns defining duplicates

La requête ci-dessus est courte et répertorie les noms des colonnes une seule fois. NOT IN (SELECT ...) est un style de requête délicat lorsque des valeurs NULL peuvent être impliquées, mais que la colonne système ctid n'est jamais NULL. Voir:

Utiliser EXISTS comme démontré par @Gordon est généralement plus rapide. Donc, une auto-jointure avec la clause USINGcomme @isapir ajouté plus tard . Les deux doivent aboutir au même plan de requête. 

Notez toutefois une différence importante: ces autres requêtes traitent les valeurs NULL comme non égal à, tandis que GROUP BY (ou DISTINCT ou DISTINCT ON () ) traite les valeurs NULL comme égal. Peu importe si les colonnes de clé sont définies NOT NULL. Sinon, selon votre définition de «dupliquer», vous aurez besoin de l'une ou l'autre approche. Ou utilise IS NOT DISTINCT FROM pour comparer des valeurs (qui peuvent ne pas être en mesure d'utiliser certains index).

Avertissement:

ctid est un détail d'implémentation interne de Postgres, il ne fait pas partie du standard SQL et peut être modifié sans avertissement entre les versions majeures (même si cela est très peu probable). Ses valeurs peuvent changer d'une commande à l'autre en raison de processus en arrière-plan ou d'opérations d'écriture simultanées (mais pas dans la même commande).

En relation:

De côté:

La cible d'une instruction DELETE ne peut pas être le CTE, mais uniquement la table sous-jacente. C'est un effet d'entraînement de SQL Server - tout comme votre approche globale.

17

Voici ce que j'ai trouvé, en utilisant un group by

DELETE FROM mytable
WHERE id NOT in (
  SELECT MIN(id) 
  FROM mytable
  GROUP BY name, address, zipcode
)

Il supprime les doublons tout en préservant l'enregistrement le plus ancien comportant des doublons.

10
Bruno Calza

Nous pouvons utiliser une fonction de fenêtre pour éliminer très efficacement les lignes en double:

DELETE FROM tab 
  WHERE id IN (SELECT id 
                  FROM (SELECT row_number() OVER (PARTITION BY column_with_duplicate_values), id 
                           FROM tab) x 
                 WHERE x.row_number > 1);

Quelques versions optimisées de PostgreSQL (avec ctid):

DELETE FROM tab 
  WHERE ctid = ANY(ARRAY(SELECT ctid 
                  FROM (SELECT row_number() OVER (PARTITION BY column_with_duplicate_values), ctid 
                           FROM tab) x 
                 WHERE x.row_number > 1));
6
Vivek S.

La syntaxe valide est spécifiée à http://www.postgresql.org/docs/current/static/sql-delete.html

Je voudrais ALTER votre table pour ajouter un identifiant de clé primaire auto-incrémenté unique afin que vous puissiez exécuter une requête comme celle-ci qui conservera le premier de chaque ensemble de doublons (c'est-à-dire celui avec l'identifiant le plus bas). Notez que l’ajout de la clé est un peu plus compliqué dans Postgres que dans d’autres DB. 

DELETE FROM mytable d USING (
  SELECT min(id), name, address, Zip 
  FROM mytable 
  GROUP BY name, address, Zip HAVING COUNT() > 1
) AS k 
WHERE d.id <> k.id 
AND d.name=k.name 
AND d.address=k.address 
AND d.Zip=k.Zip;
2
Joe Murray

Si vous souhaitez conserver une ligne sur les lignes en double de la table.

create table some_name_for_new_table as 
(select * from (select *,row_number() over (partition by pk_id) row_n from 
your_table_name_where_duplicates_are_present) a where row_n = 1);

Cela va créer une table que vous pouvez copier. 

Avant de copier le tableau, supprimez la colonne 'row_n' 

1
Aditya Nathireddy

Si vous voulez un identifiant unique pour chaque ligne, vous pouvez simplement en ajouter un (une série ou un guid) et le traiter comme une clé de substitution.


CREATE TABLE thenames
        ( name text not null
        , address text not null
        , zipcode text not null
        );
INSERT INTO thenames(name,address,zipcode) VALUES
('James', 'main street', '123' )
,('James', 'main street', '123' )
,('James', 'void street', '456')
,('Alice', 'union square' , '123')
        ;

SELECT*FROM thenames;

        -- add a surrogate key
ALTER TABLE thenames
        ADD COLUMN seq serial NOT NULL PRIMARY KEY
        ;
SELECT*FROM thenames;

DELETE FROM thenames del
WHERE EXISTS(
        SELECT*FROM thenames x
        WHERE x.name=del.name
        AND x.address=del.address
        AND x.zipcode=del.zipcode
        AND x.seq < del.seq
        );

        -- add the unique constrain,so that new dupplicates cannot be created in the future
ALTER TABLE thenames
        ADD UNIQUE (name,address,zipcode)
        ;

SELECT*FROM thenames;
0
wildplasser

Dans la documentation supprimez les lignes en double

Une question fréquente dans IRC est de savoir comment supprimer les lignes dupliquées sur un ensemble de colonnes, en ne conservant que celle dont l'ID est le plus bas . Cette requête s'applique à toutes les lignes de nomtable ayant la même colonne et column3.

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

Parfois, un champ d'horodatage est utilisé à la place d'un champ ID.

0
Chad Crowe