web-dev-qa-db-de.com

Entfernen Sie doppelte Zeilen in MySQL

Ich habe eine Tabelle mit folgenden Feldern:

id (Unique)
url (Unique)
title
company
site_id

Jetzt muss ich Zeilen mit demselben title, company and site_id entfernen. Eine Möglichkeit, dies zu tun, wird die folgende SQL mit einem Skript (PHP) verwenden:

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

Nach dem Ausführen dieser Abfrage kann ich Duplikate mithilfe eines serverseitigen Skripts entfernen.

Ich möchte jedoch wissen, ob dies nur mit SQL-Abfragen möglich ist.

318
Chetan

Ein wirklich einfacher Weg, dies zu tun, ist das Hinzufügen eines UNIQUE-Index für die 3 Spalten. Wenn Sie die Anweisung ALTER schreiben, schließen Sie das Schlüsselwort IGNORE ein. So wie:

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

Dadurch werden alle doppelten Zeilen gelöscht. Als zusätzlicher Vorteil werden zukünftige INSERTs, die Duplikate sind, fehlerhaft ausgegeben. Wie immer möchten Sie vielleicht ein Backup machen, bevor Sie so etwas ausführen ...

573
Chris Henry

Wenn Sie die Spalteneigenschaften nicht ändern möchten, können Sie die folgende Abfrage verwenden.

Da Sie über eine Spalte mit eindeutigen IDs verfügen (z. B. auto_increment-Spalten), können Sie sie zum Entfernen der Duplikate verwenden:

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

In MySQL können Sie es mit dem NULL-sicheren Gleichheitsoperator (aka "Spaceship Operator" ) noch weiter vereinfachen:

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 hat Einschränkungen hinsichtlich der Bezugnahme auf die Tabelle, aus der Sie löschen. Sie können dies mit einer temporären Tabelle umgehen, z.

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

Aus Kostanos 'Vorschlag in den Kommentaren:
. Die einzige langsame Abfrage oben ist DELETE, wenn Sie eine sehr große Datenbank haben. Diese Abfrage könnte schneller sein: 

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

Wenn die IGNORE-Anweisung nicht wie in meinem Fall funktioniert, können Sie die folgende Anweisung verwenden:

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

Es gibt eine andere Lösung:

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

Das Löschen von Duplikaten in MySQL-Tabellen ist ein häufiges Problem. Normalerweise ist dies das Ergebnis einer fehlenden Einschränkung, um diese Duplikate zu vermeiden. Diese häufige Problematik bringt jedoch meist spezifische Bedürfnisse mit sich, die spezifische Ansätze erfordern. Der Ansatz sollte unterschiedlich sein, z. B. abhängig von der Größe der Daten, von dem doppelten Eintrag, der aufbewahrt werden sollte (in der Regel der erste oder der letzte), ob es Indizes gibt, die aufbewahrt werden müssen, oder ob wir einen zusätzlichen Eintrag vornehmen möchten Aktion für die duplizierten Daten.

Es gibt auch einige Besonderheiten in MySQL selbst, z. B. dass Sie nicht die gleiche Tabelle auf eine FROM-Ursache verweisen können, wenn Sie eine Tabelle UPDATE ausführen (dies führt zu MySQL-Fehler # 1093). Diese Einschränkung kann überwunden werden, indem eine innere Abfrage mit einer temporären Tabelle verwendet wird (wie in einigen Ansätzen oben vorgeschlagen). Diese innere Abfrage funktioniert jedoch nicht besonders gut, wenn mit großen Datenquellen gearbeitet wird.

Es gibt jedoch einen besseren Ansatz zum Entfernen von Duplikaten, der sowohl effizient als auch zuverlässig ist und leicht an unterschiedliche Bedürfnisse angepasst werden kann.

Die allgemeine Idee ist, eine neue temporäre Tabelle zu erstellen, die in der Regel eine eindeutige Einschränkung hinzufügt, um weitere Duplikate zu vermeiden, und die Daten aus Ihrer früheren Tabelle in die neue einzufügen, während sie sich um die Duplikate kümmern. Dieser Ansatz basiert auf einfachen MySQL-INSERT-Abfragen, erstellt eine neue Einschränkung, um weitere Duplikate zu vermeiden, und überspringt die Notwendigkeit, eine innere Abfrage zu verwenden, um nach Duplikaten zu suchen, und einer temporären Tabelle, die im Speicher aufbewahrt werden sollte (und somit auch große Datenquellen passt).

So kann es erreicht werden. Dazu haben wir eine Tabelle employee mit den folgenden Spalten:

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

Um die Zeilen mit einer doppelten Spalte ssn zu löschen und nur den ersten gefundenen Eintrag beizubehalten, kann wie folgt vorgegangen werden:

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

Technische Erklärung

  • Zeile 1 erstellt eine neue Tabelle tmp_eployee mit derselben Struktur wie die Tabelle employee
  • Zeile 2 fügt der neuen Tabelle tmp_eployee eine UNIQUE-Einschränkung hinzu, um weitere Duplikate zu vermeiden
  • Zeile 3 scannt die ursprüngliche employee -Tabelle nach ID und fügt neue Mitarbeitereinträge in die neue tmp_eployee -Tabelle ein, wobei doppelte Einträge ignoriert werden
  • Zeile 4 benennt die Tabellen um, sodass die neue Tabelle employee alle Einträge ohne die Duplikate enthält und eine Sicherungskopie der früheren Daten in der Tabelle backup_employee gespeichert wird

Bei diesem Ansatz wurden 1.6M-Register in weniger als 200s in 6k umgewandelt.

Chetan Nach diesem Vorgang können Sie schnell und einfach alle Ihre Duplikate entfernen und eine UNIQUE-Einschränkung erstellen, indem Sie Folgendes ausführen:

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;

Natürlich kann dieser Prozess weiter modifiziert werden, um ihn beim Löschen von Duplikaten an unterschiedliche Anforderungen anzupassen. Einige Beispiele folgen.

✔ Variante, um den letzten Eintrag anstelle des ersten zu behalten

Manchmal müssen wir den letzten duplizierten Eintrag anstelle des ersten aufbewahren.

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;
  • In Zeile 3 gibt die Klausel ORDER BY id DESC die letzten IDs an, die Priorität gegenüber dem Rest haben

✔ Variation für die Ausführung einiger Aufgaben an den Duplikaten, z. B. das Zählen der gefundenen Duplikate

Manchmal müssen wir die gefundenen doppelten Einträge weiter bearbeiten (z. B. das Zählen der Duplikate).

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;
  • In Zeile 3 wird eine neue Spalte n_duplicates erstellt
  • In Zeile 4 wird mit der Abfrage INSERT INTO ... ON DUPLICATE KEY UPDATE eine zusätzliche Aktualisierung durchgeführt, wenn ein Duplikat gefunden wird (in diesem Fall einen Zähler erhöhen) Das INSERT INTO. .. ON DUPLICATE KEY UPDATE Abfrage kann verwendet werden, um verschiedene Arten von Aktualisierungen für die gefundenen Duplikate durchzuführen. 

✔ Variante zur Regenerierung der automatisch inkrementellen Feld-ID

Manchmal verwenden wir ein Auto-Incremental-Feld. Um den Index so kompakt wie möglich zu halten, können wir die Löschung der Duplikate nutzen, um das Auto-Incremental-Feld in der neuen temporären Tabelle neu zu generieren.

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;
  • In Zeile 3 wird anstelle der Auswahl aller Felder in der Tabelle das ID-Feld übersprungen, sodass die DB-Engine automatisch ein neues generiert

✔ Weitere Varianten

Je nach gewünschtem Verhalten sind auch viele weitere Modifikationen möglich. Als Beispiel verwenden die folgenden Abfragen eine zweite temporäre Tabelle, um neben 1) den letzten Eintrag anstelle des ersten zu behalten; und 2) Erhöhen eines Zählers auf den gefundenen Duplikaten; auch 3) regeneriere die automatisch inkrementelle Feld-ID, wobei die Eingabereihenfolge wie in den vorherigen Daten beibehalten wird.

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

Ich habe dieses Abfrage-Snipet für SQL Server, aber ich denke, es kann in anderen DBMS mit kleinen Änderungen verwendet werden:

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

Ich habe vergessen zu sagen, dass diese Abfrage die Zeile mit der niedrigsten ID der duplizierten Zeilen nicht entfernt. Wenn dies für Sie funktioniert, versuchen Sie diese Abfrage: 

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

Einfach und schnell für alle Fälle:

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

Der schnellere Weg ist das Einfügen unterschiedlicher Zeilen in eine temporäre Tabelle. Mit delete habe ich einige Stunden gebraucht, um Duplikate aus einer Tabelle mit 8 Millionen Zeilen zu entfernen. Mit Einsatz und Unterscheidung dauerte es nur 13 Minuten. 

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

Ich besuche diese Seite jedes Mal, wenn ich google "entferne Duplikate aus MySQL", aber für meine theIGNORE-Lösungen funktioniert das nicht, weil ich InnoDB-MySQL-Tabellen habe

dieser Code funktioniert jederzeit besser

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 = der Name der zu reinigenden Tabelle

tableToclean_temp = Eine temporäre Tabelle erstellt und gelöscht

3
Francesco

Eine Lösung, die einfach zu verstehen ist und ohne Primärschlüssel funktioniert: 

1) füge eine neue boolesche Spalte hinzu

alter table mytable add tokeep boolean;

2) Fügen Sie eine Einschränkung für die duplizierten Spalten UND die neue Spalte hinzu

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

3) Setzen Sie die boolesche Spalte auf true. Dies wird aufgrund der neuen Einschränkung nur in einer der duplizierten Zeilen gelingen

update ignore mytable set tokeep = true;

4) Löschen Sie Zeilen, die nicht als Tokeep markiert wurden

delete from mytable where tokeep is null;

5) lassen Sie die hinzugefügte Säule fallen

alter table mytable drop tokeep;

Ich empfehle Ihnen, die von Ihnen hinzugefügte Einschränkung beizubehalten, damit in Zukunft neue Duplikate vermieden werden.

2
xtian

Diese Lösung wird die Duplikate in eine Tabelle verschieben und die Uniques in eine andere .

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

wenn Sie eine große Tabelle mit einer großen Anzahl von Datensätzen haben, funktionieren die oben genannten Lösungen nicht oder dauern zu lange. Dann haben wir eine andere Lösung 

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

Löschen Sie doppelte Zeilen mit der DELETE JOIN-Anweisung MySQL stellt Ihnen die DELETE JOIN-Anweisung zur Verfügung, mit der Sie doppelte Zeilen schnell entfernen können.

Die folgende Anweisung löscht doppelte Zeilen und behält die höchste ID bei:

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

Ich habe einen einfachen Weg gefunden. (Bleib auf dem Laufenden)

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

Ab Version 8.0 (2018) unterstützt MySQL schließlich window-Funktionen

Fensterfunktionen sind sowohl praktisch als auch effizient. Hier ist eine Lösung, die zeigt, wie Sie diese Aufgabe lösen können.

In einer Unterabfrage können wir ROW_NUMBER() verwenden, um jedem Datensatz in der Tabelle innerhalb von column1/column2-Gruppen eine Position zuzuweisen, sortiert nach id. Wenn keine Duplikate vorhanden sind, erhält der Datensatz die Zeilennummer 1. Wenn Duplikate vorhanden sind, werden sie durch aufsteigende id (beginnend mit 1) nummeriert.

Sobald Datensätze in der Unterabfrage ordnungsgemäß nummeriert sind, werden durch die äußere Abfrage alle Datensätze gelöscht, deren Zeilennummer nicht 1 ist.

Abfrage:

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

Um Datensätze mit eindeutigen Spalten zu duplizieren, z. COL1, COL2, COL3 sollten nicht repliziert werden. (Angenommen, wir haben 3 Spalten übersehen, die in der Tabellenstruktur eindeutig sind, und es wurden mehrere doppelte Einträge in die Tabelle vorgenommen.)

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;

Hoffnung hilft dev.

0
Abdul Rehman

TL; TR;

Ein ausführlich beschriebenes Tutorial zur Lösung dieses Problems finden Sie auf der mysqltutorial.org Site:

So löschen Sie doppelte Zeilen in MySQL

Es wird sehr deutlich gezeigt, wie doppelte Zeilen auf drei verschiedene Arten gelöscht werden :

A) Using DELETE JOIN statement

B) Verwenden einer Zwischentabelle

C) Verwenden der Funktion ROW_NUMBER()

Ich hoffe, dass es jemandem hilft.

0
simhumileco

So löschen Sie den doppelten Datensatz in einer Tabelle.

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

oder

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