web-dev-qa-db-fra.com

Supprimer les lignes en double dans MySQL

J'ai une table avec les champs suivants:

id (Unique)
url (Unique)
title
company
site_id

Maintenant, je dois supprimer les lignes ayant le même title, company and site_id. Une façon de le faire consiste à utiliser le code SQL suivant avec un script (PHP):

SELECT title, site_id, location, id, count( * ) 
FROM jobs
GROUP BY site_id, company, title, location
HAVING count( * ) >1

Après avoir exécuté cette requête, je peux supprimer les doublons à l'aide d'un script côté serveur.

Mais, je veux savoir si cela peut être fait uniquement en utilisant une requête SQL.

318
Chetan

Une méthode très simple consiste à ajouter un index UNIQUE sur les 3 colonnes. Lorsque vous écrivez l'instruction ALTER, incluez le mot clé IGNORE. Ainsi:

ALTER IGNORE TABLE jobs
ADD UNIQUE INDEX idx_name (site_id, title, company);

Cela supprimera toutes les lignes en double. En tant qu'avantage supplémentaire, les futures variables INSERTs qui se trouvent en double seront susceptibles d'erreur Comme toujours, vous voudrez peut-être faire une sauvegarde avant d'exécuter quelque chose comme ça ...

573
Chris Henry

Si vous ne souhaitez pas modifier les propriétés de la colonne, vous pouvez utiliser la requête ci-dessous.

Puisque vous avez une colonne qui a des identifiants uniques (par exemple, des colonnes auto_increment), vous pouvez l’utiliser pour supprimer les doublons:

DELETE `a`
FROM
    `jobs` AS `a`,
    `jobs` AS `b`
WHERE
    -- IMPORTANT: Ensures one version remains
    -- Change "ID" to your unique column's name
    `a`.`ID` < `b`.`ID`

    -- Any duplicates you want to check for
    AND (`a`.`title` = `b`.`title` OR `a`.`title` IS NULL AND `b`.`title` IS NULL)
    AND (`a`.`company` = `b`.`company` OR `a`.`company` IS NULL AND `b`.`company` IS NULL)
    AND (`a`.`site_id` = `b`.`site_id` OR `a`.`site_id` IS NULL AND `b`.`site_id` IS NULL);

Dans MySQL, vous pouvez le simplifier encore davantage avec l'opérateur égal NULL-safe (ou "opérateur de vaisseau spatial" ):

DELETE `a`
FROM
    `jobs` AS `a`,
    `jobs` AS `b`
WHERE
    -- IMPORTANT: Ensures one version remains
    -- Change "ID" to your unique column's name
    `a`.`ID` < `b`.`ID`

    -- Any duplicates you want to check for
    AND `a`.`title` <=> `b`.`title`
    AND `a`.`company` <=> `b`.`company`
    AND `a`.`site_id` <=> `b`.`site_id`;
147
rehriff

MySQL a des restrictions quant à la référence à la table que vous supprimez. Vous pouvez contourner cela avec une table temporaire, comme:

create temporary table tmpTable (id int);

insert  tmpTable
        (id)
select  id
from    YourTable yt
where   exists
        (
        select  *
        from    YourTabe yt2
        where   yt2.title = yt.title
                and yt2.company = yt.company
                and yt2.site_id = yt.site_id
                and yt2.id > yt.id
        );

delete  
from    YourTable
where   ID in (select id from tmpTable);

De la suggestion de Kostanos dans les commentaires:
La seule requête lente ci-dessus est DELETE, dans les cas où vous avez une très grande base de données. Cette requête pourrait être plus rapide: 

DELETE FROM YourTable USING YourTable, tmpTable WHERE YourTable.id=tmpTable.id
72
Andomar

Si l'instruction IGNORE ne fonctionne pas comme dans mon cas, vous pouvez utiliser l'instruction ci-dessous:

CREATE TABLE your_table_deduped like your_table;
INSERT your_table_deduped SELECT * FROM your_table GROUP BY index1_id, index2_id;
RENAME TABLE your_table TO your_table_with_dupes;
RENAME TABLE your_table_deduped TO your_table;
#OPTIONAL
ALTER TABLE `your_table` ADD UNIQUE `unique_index` (`index1_id`, `index2_id`);
#OPTIONAL
DROP TABLE your_table_with_dupes;
37
Kamil

Il y a une autre solution:

DELETE t1 FROM my_table t1, my_table t2 WHERE t1.id < t2.id AND t1.my_field = t2.my_field AND t1.my_field_2 = t2.my_field_2 AND ...
23
Mostafa -T

La suppression de doublons sur les tables MySQL est un problème courant. Elle résulte généralement d’une contrainte manquante qui permet d’éviter ces doublons au préalable. Mais ce problème commun s'accompagne généralement de besoins spécifiques… qui nécessitent des approches spécifiques. L’approche doit être différente en fonction, par exemple, de la taille des données, de l’entrée dupliquée à conserver (en général la première ou la dernière), de la possibilité de conserver des index ou de la nécessité d’effectuer des opérations supplémentaires. action sur les données dupliquées.

Il y a aussi quelques spécificités sur MySQL lui-même, comme l'impossibilité de faire référence à la même table sur une cause FROM lors de l'exécution d'une table UPDATE (cela provoquera l'erreur MySQL # 1093). Cette limitation peut être surmontée en utilisant une requête interne avec une table temporaire (comme suggéré dans certaines approches ci-dessus). Mais cette requête interne ne fonctionnera pas particulièrement bien avec les sources de données volumineuses.

Cependant, une meilleure approche existe pour supprimer les doublons, à la fois efficace et fiable, et pouvant être facilement adaptée à différents besoins.

L'idée générale est de créer une nouvelle table temporaire, en ajoutant généralement une contrainte unique pour éviter les doublons, et d'insérer les données de votre ancienne table dans la nouvelle, tout en prenant soin des doublons. Cette approche repose sur de simples requêtes MySQL INSERT, crée une nouvelle contrainte pour éviter les doublons et évite la nécessité d'utiliser une requête interne pour rechercher des doublons et une table temporaire devant être conservée en mémoire (ajustant ainsi les sources de données volumineuses).

Voici comment cela peut être réalisé. Étant donné que nous avons une table employee, avec les colonnes suivantes:

employee (id, first_name, last_name, start_date, ssn)

Afin de supprimer les lignes avec une colonne dupliquée ssn et de ne conserver que la première entrée trouvée, le processus suivant peut être suivi:

-- create a new tmp_eployee table
CREATE TABLE tmp_employee LIKE employee;

-- add a unique constraint
ALTER TABLE tmp_employee ADD UNIQUE(ssn);

-- scan over the employee table to insert employee entries
INSERT IGNORE INTO tmp_employee SELECT * FROM employee ORDER BY id;

-- rename tables
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;

Explication technique

  • La ligne 1 crée une nouvelle table tmp_eployee avec exactement la même structure que la table employee
  • La ligne 2 ajoute une contrainte UNIQUE à la nouvelle table tmp_eployee pour éviter toute duplication supplémentaire.
  • La ligne 3 analyse la table d'origine employee par son identifiant, en insérant de nouvelles entrées d'employé dans la nouvelle table tmp_eployee tout en ignorant les entrées dupliquées.
  • La ligne 4 renomme les tables pour que la nouvelle table employee contienne toutes les entrées sans les doublons et qu'une copie de sauvegarde des anciennes données soit conservée dans la table backup_employee.

⇒ _ {En utilisant cette approche, 1,6 million de registres ont été convertis en 6k en moins de 200s.}

Chetan , en suivant ce processus, vous pouvez rapidement et facilement supprimer tous vos doublons et créer une contrainte UNIQUE en exécutant:

CREATE TABLE tmp_jobs LIKE jobs;

ALTER TABLE tmp_jobs ADD UNIQUE(site_id, title, company);

INSERT IGNORE INTO tmp_jobs SELECT * FROM jobs ORDER BY id;

RENAME TABLE jobs TO backup_jobs, tmp_jobs TO jobs;

Bien sûr, ce processus peut être encore modifié pour l'adapter à différents besoins lors de la suppression de doublons. Quelques exemples suivent.

✔ Variation pour conserver la dernière entrée au lieu de la première

Parfois, nous devons conserver la dernière entrée dupliquée au lieu de la première.

CREATE TABLE tmp_employee LIKE employee;

ALTER TABLE tmp_employee ADD UNIQUE(ssn);

INSERT IGNORE INTO tmp_employee SELECT * FROM employee ORDER BY id DESC;

RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
  • Sur la ligne 3, la clause ORDER BY id DESC donne le dernier identifiant à avoir la priorité sur le reste

✔ Variation permettant d’effectuer certaines tâches sur les doublons, par exemple, en comptant le nombre de doublons trouvés

Parfois, nous devons effectuer un traitement supplémentaire sur les entrées dupliquées trouvées (telles que la gestion du nombre de doublons).

CREATE TABLE tmp_employee LIKE employee;

ALTER TABLE tmp_employee ADD UNIQUE(ssn);

ALTER TABLE tmp_employee ADD COLUMN n_duplicates INT DEFAULT 0;

INSERT INTO tmp_employee SELECT * FROM employee ORDER BY id ON DUPLICATE KEY UPDATE n_duplicates=n_duplicates+1;

RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
  • Sur la ligne 3, une nouvelle colonne n_duplicates est créée
  • Sur la ligne 4, la requête INSERT INTO ... ON DUPLICATE KEY UPDATE est utilisée pour effectuer une mise à jour supplémentaire lorsqu'un doublon est trouvé (dans ce cas, augmenter un compteur) Le INSERT INTO. .. La requête ON DUPLICATE KEY UPDATE peut être utilisée pour effectuer différents types de mises à jour pour les doublons trouvés. 

✔ Variation pour la régénération de l'identifiant de champ auto-incrémental

Parfois, nous utilisons un champ auto-incrémental et, afin de garder l'index aussi compact que possible, nous pouvons tirer parti de la suppression des doublons pour régénérer le champ auto-incrémental de la nouvelle table temporaire.

CREATE TABLE tmp_employee LIKE employee;

ALTER TABLE tmp_employee ADD UNIQUE(ssn);

INSERT IGNORE INTO tmp_employee SELECT (first_name, last_name, start_date, ssn) FROM employee ORDER BY id;

RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
  • Sur la ligne 3, au lieu de sélectionner tous les champs de la table, le champ id est ignoré afin que le moteur de base de données en génère automatiquement un nouveau.

✔ Autres variations

De nombreuses autres modifications sont également réalisables en fonction du comportement souhaité. Par exemple, les requêtes suivantes utiliseront une seconde table temporaire pour, en plus de 1) conserver la dernière entrée au lieu de la première; et 2) augmenter un compteur sur les doublons trouvés; également 3) régénérer l'identifiant de champ auto-incrémental tout en conservant l'ordre de saisie tel qu'il était sur les données précédentes.

CREATE TABLE tmp_employee LIKE employee;

ALTER TABLE tmp_employee ADD UNIQUE(ssn);

ALTER TABLE tmp_employee ADD COLUMN n_duplicates INT DEFAULT 0;

INSERT INTO tmp_employee SELECT * FROM employee ORDER BY id DESC ON DUPLICATE KEY UPDATE n_duplicates=n_duplicates+1;

CREATE TABLE tmp_employee2 LIKE tmp_employee;

INSERT INTO tmp_employee2 SELECT (first_name, last_name, start_date, ssn) FROM tmp_employee ORDER BY id;

DROP TABLE tmp_employee;

RENAME TABLE employee TO backup_employee, tmp_employee2 TO employee;
21

J'ai ce snipet de requête pour SQLServer mais je pense qu'il peut être utilisé dans d'autres SGBD avec de petites modifications:

DELETE
FROM Table
WHERE Table.idTable IN  (  
    SELECT MAX(idTable)
    FROM idTable
    GROUP BY field1, field2, field3
    HAVING COUNT(*) > 1)

J'ai oublié de vous dire que cette requête ne supprime pas la ligne avec l'id le plus bas des lignes dupliquées. Si cela fonctionne pour vous essayez cette requête: 

DELETE
FROM jobs
WHERE jobs.id IN  (  
    SELECT MAX(id)
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING COUNT(*) > 1)
6
Eduardo Rascon

Simple et rapide pour tous les cas:

CREATE TEMPORARY TABLE IF NOT EXISTS _temp_duplicates AS (SELECT dub.id FROM table_with_duplications dub GROUP BY dub.field_must_be_uniq_1, dub.field_must_be_uniq_2 HAVING COUNT(*)  > 1);

DELETE FROM table_with_duplications WHERE id IN (SELECT id FROM _temp_duplicates);
4
artemiuz

Le moyen le plus rapide consiste à insérer des lignes distinctes dans une table temporaire. En utilisant delete, il m'a fallu quelques heures pour supprimer les doublons d'un tableau de 8 millions de lignes. En utilisant insert et distinct, cela n'a pris que 13 minutes. 

CREATE TABLE tempTableName LIKE tableName;  
CREATE INDEX ix_all_id ON tableName(cellId,attributeId,entityRowId,value);  
INSERT INTO tempTableName(cellId,attributeId,entityRowId,value) SELECT DISTINCT cellId,attributeId,entityRowId,value FROM tableName;  
TRUNCATE TABLE tableName;
INSERT INTO tableName SELECT * FROM tempTableName; 
DROP TABLE tempTableName;  
4
Nav

Je continue à visiter cette page chaque fois que je recherche "supprimer les doublons de MySQL", mais les solutions theIGNORE ne fonctionnent pas car je dispose de tables InnoDB MySQL

ce code fonctionne mieux à tout moment

CREATE TABLE tableToclean_temp LIKE tableToclean;
ALTER TABLE tableToclean_temp ADD UNIQUE INDEX (fontsinuse_id);
INSERT IGNORE INTO tableToclean_temp SELECT * FROM tableToclean;
DROP TABLE tableToclean;
RENAME TABLE tableToclean_temp TO tableToclean;

tableToclean = le nom de la table à nettoyer

tableToclean_temp = une table temporaire créée et supprimée

3
Francesco

Une solution simple à comprendre et qui fonctionne sans clé primaire: 

1) ajouter une nouvelle colonne booléenne

alter table mytable add tokeep boolean;

2) ajouter une contrainte sur les colonnes dupliquées ET la nouvelle colonne

alter table mytable add constraint preventdupe unique (mycol1, mycol2, tokeep);

3) définissez la colonne booléenne sur true. Cela ne réussira que sur l'une des lignes dupliquées à cause de la nouvelle contrainte

update ignore mytable set tokeep = true;

4) supprimer les lignes qui n'ont pas été marquées comme étant à conserver

delete from mytable where tokeep is null;

5) déposer la colonne ajoutée

alter table mytable drop tokeep;

Je vous suggère de conserver la contrainte que vous avez ajoutée afin d'éviter de nouveaux doublons à l'avenir.

2
xtian

Cette solution déplacera les doublons dans une table et les uniques dans une autre.

-- speed up creating uniques table if dealing with many rows
CREATE INDEX temp_idx ON jobs(site_id, company, title, location);

-- create the table with unique rows
INSERT jobs_uniques SELECT * FROM
    (
    SELECT * 
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING count(1) > 1
    UNION
    SELECT *
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING count(1) = 1
) x

-- create the table with duplicate rows
INSERT jobs_dupes 
SELECT * 
FROM jobs
WHERE id NOT IN
(SELECT id FROM jobs_uniques)

-- confirm the difference between uniques and dupes tables
SELECT COUNT(1)
AS jobs, 
(SELECT COUNT(1) FROM jobs_dupes) + (SELECT COUNT(1) FROM jobs_uniques)
AS sum
FROM jobs
2
Anthony Vipond

si vous avez une grande table avec un très grand nombre d'enregistrements, les solutions ci-dessus ne fonctionneront pas ou prendront trop de temps. Ensuite, nous avons une solution différente 

-- Create temporary table

CREATE TABLE temp_table LIKE table1;

-- Add constraint
ALTER TABLE temp_table ADD UNIQUE(title, company,site_id);

-- Copy data
INSERT IGNORE INTO temp_table SELECT * FROM table1;

-- Rename and drop
RENAME TABLE table1 TO old_table1, temp_table TO table1;
DROP TABLE old_table1;
2
faisalbhagat

Supprimer les lignes en double à l’aide de l’instruction DELETE JOIN MySQL vous fournit l’instruction DELETE JOIN que vous pouvez utiliser pour supprimer rapidement les lignes en double.

L'instruction suivante supprime les lignes en double et conserve l'identifiant le plus élevé:

DELETE t1 FROM contacts t1
    INNER JOIN
contacts t2 WHERE
t1.id < t2.id AND t1.email = t2.email;
1
Saad Mirza

J'ai trouvé un moyen simple. (garder au plus tard)

DELETE t1 FROM tablename t1 INNER JOIN tablename t2 
WHERE t1.id < t2.id AND t1.column1 = t2.column1 AND t1.column2 = t2.column2;
1
Rico Nguyen

Depuis la version 8.0 (2018), MySQL supporte enfin les fonctions window

Les fonctions de la fenêtre sont à la fois pratiques et efficaces. Voici une solution qui montre comment les utiliser pour résoudre cette tâche.

Dans une sous-requête, nous pouvons utiliser ROW_NUMBER() pour attribuer une position à chaque enregistrement de la table au sein de column1/column2 groupes, classés par id. S'il n'y a pas de doublons, l'enregistrement recevra le numéro de ligne 1. Si des doublons existent, ils seront numérotés par ordre croissant id (à partir de 1).

Une fois que les enregistrements sont correctement numérotés dans la sous-requête, la requête externe supprime simplement tous les enregistrements dont le numéro de ligne n'est pas 1.

Requête:

DELETE FROM tablename
WHERE id IN (
    SELECT id
    FROM (
        SELECT 
            id, 
            ROW_NUMBER() OVER(PARTITION BY column1, column2 ORDER BY id) rn
        FROM output
    ) t
    WHERE rn > 1
)
1
GMB

Pour dupliquer des enregistrements avec des colonnes uniques, p. Ex. COL1, COL2, COL3 ne doivent pas être répliqués (supposons que nous ayons oublié 3 colonnes uniques dans la structure de la table et que plusieurs entrées en double aient été créées dans la table)

DROP TABLE TABLE_NAME_copy;
CREATE TABLE TABLE_NAME_copy LIKE TABLE_NAME;
INSERT INTO TABLE_NAME_copy
SELECT * FROM TABLE_NAME
GROUP BY COLUMN1, COLUMN2, COLUMN3; 
DROP TABLE TABLE_NAME;
ALTER TABLE TABLE_NAME_copy RENAME TO TABLE_NAME;

Espoir aidera dev.

0
Abdul Rehman

TL; TR;

Un tutoriel très décrit pour résoudre ce problème peut être trouvé sur mysqltutorial.org site:

Comment supprimer les lignes en double dans MySQL

Il est très clairement montré comment supprimer les lignes en double de trois manières différentes :

A) Utilisation de l'instruction DELETE JOIN

B) Utilisation d'une table intermédiaire

C) Utilisation de la fonction ROW_NUMBER()

J'espère que ça va aider quelqu'un.

0
simhumileco

Supprimer l'enregistrement en double dans une table.

delete from job s 
where rowid < any 
(select rowid from job k 
where s.site_id = k.site_id and 
s.title = k.title and 
s.company = k.company);

ou

delete from job s 
where rowid not in 
(select max(rowid) from job k 
where s.site_id = k.site_id and
s.title = k.title and 
s.company = k.company);
0
Arun Solomon
-- Here is what I used, and it works:
create table temp_table like my_table;
-- t_id is my unique column
insert into temp_table (id) select id from my_table GROUP by t_id;
delete from my_table where id not in (select id from temp_table);
drop table temp_table;
0
Duy Hoang