MySQL: Doppelte Datensätze löschen

| 6 Kommentare

Hin und wieder kann es vorkommen, dass sich Datensätze ungewollt doppelt in der Datenbank wiederfinden.

Hier ein kleiner Spickzettel, wie sich die Doubletten wieder entfernen lassen.

Die Struktur der Testtabelle sieht folgendermaßen aus:

CREATE TABLE `sqltest`.`duplicates` (
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `a` VARCHAR(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `b` INT(11) NOT NULL ,
  `c` DATETIME NOT NULL ,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;

Noch ein paar Daten rein und fertig ist die Spielwiese.

mysql> select * from duplicates;
+----+--------+-----+---------------------+
| id | a      | b   | c                   |
+----+--------+-----+---------------------+
|  1 | Apfel  | 123 | 2011-01-11 17:00:00 |
|  2 | Apfel  | 123 | 2011-01-11 17:00:00 |
|  3 | Banane | 234 | 2011-01-12 17:00:00 |
|  4 | Banane | 234 | 2011-01-12 17:00:00 |
|  5 | Birne  | 456 | 2011-01-15 17:00:00 |
|  6 | Birne  | 456 | 2011-01-15 17:00:00 |
+----+--------+-----+---------------------+
6 rows in set (0.00 sec)

Die erste Abfrage macht einen JOIN auf sich selbst, dafür die Aliasse d1 und d2 und dient zunächst der Prüfung, ob wir die gewünschten Datensätze geliefert bekommen.

SELECT d1.* FROM duplicates d1, duplicates d2
 WHERE d1.id != d2.id
   AND d1.a = d2.a
   AND d1.b = d2.b
   AND d1.c = d2.c
   AND d1.id < d2.id

Das Ergebnis:

+----+--------+-----+---------------------+
| id | a      | b   | c                   |
+----+--------+-----+---------------------+
|  1 | Apfel  | 123 | 2011-01-11 17:00:00 |
|  3 | Banane | 234 | 2011-01-12 17:00:00 |
|  5 | Birne  | 456 | 2011-01-15 17:00:00 |
+----+--------+-----+---------------------+
3 rows in set (0.00 sec)

MySQL kann offensichtlich keine Daten löschen, wenn die Löschaktion auf derselben Tabelle wie die Subquery gemacht wird. Darum schreiben wir die Ids in eine temporäre Tabelle.

CREATE TABLE duplicates_ids AS (
  SELECT d2.id FROM duplicates d1, duplicates d2
   WHERE d1.id != d2.id
     AND d1.a = d2.a
     AND d1.b = d2.b
     AND d1.c = d2.c
     AND d1.id < d2.id
)

Im letzten Schritt löschen wir die zuvor ausgewählten Datensätze und auch die temporäre Tabelle. Je nachdem was wir behalten wollen, noch ggf. ein “NOT” vor das “IN” schreiben.

DELETE FROM duplicates WHERE id [NOT] IN (SELECT * FROM duplicates_ids);
DROP TABLE duplicates_ids;

Achtung! Ich hafte nicht bei Datenverlust oder sonstigen Fehlern. Ein Backup sollte sicherheitshalber vorher gemacht werden.

6 Kommentare

  1. Gut, das haut aber nur nur dann hin, wenn es mindestens ein Unterscheidungsmerkmal zwischen den Duplikaten gibt (in deinem Fall die ID). Ich überlege grad, ob sowas (doppelte, identische Einträge) nicht direkt durch DB-interne Mechanismen im Design vermieden werden sollte (unique etc.).

  2. Im besten Fall würde man das schon während der Entwicklung berücksichtigen, aber manchmal treten gewisse Dinge auch erst später auf.

    Mindestens ein eindeutiges Merkmal muss schon sein, da solche Aktionen sonst noch unhandlicher würden. Aber auch dann lässt sich die Datenmenge natürlich reduzieren (kopieren mit DISTINCT z.B.).

  3. Eine weiter möglichkeit wäre auf die Spalte mit doppelten Einträgen nachträglich einen unique-Key zu setzen. Mit dem Schlüsselwort IGNORE löscht MySql automatisch doppelte Einträge.

    ALTER IGNORE TABLE duplicates ADD UNIQUE INDEX a

    Hier sollte es auch möglich sein mit ORDER BY (oder vielleicht eine andere Methode) die Reihenfolge festzulegen. Das hab ich jetzt aber nicht getestet.

  4. Das ist schön und gut, dass die Ids aber referentielle Abhängigkeiten mit anderen Tabellen haben könnten, wird hier gar nicht behandelt.

    Ich hatte letztens mal wieder einen schönen vermüllten Datensatz mit Nutzerregistrierungen und den unterschiedlichsten Merkmalen.
    Duplikate habe ich mit mehreren Methoden, von grob auf fein herausgesucht.. Das waren einfach mehrere Queries, die mit GROUP BY und COUNT() und HAVING COUNT() > 1 einzelne Duplikate gefunden haben. So funktioniert es auch über mehrere Tabellen hinweg mit einem JOIN.

    In einer Schleife wurden dann die tatsächlichen Duplikate mit allen IDs und weiteren Merkmalen herausgesucht.
    So konnte ich in späteren Datensätzen gegebenenfalls nicht mehr vorhandene Merkmale erhalten, mir die Reihenfolge aussuchen und mit einem Update auf die referentiellen Abhängigkeiten auf die neue ID hinweisen.

    Da das eine einmalige Aktion ist, ist die Rechenintensität zu verschmerzen..

  5. Pingback: doppelte Einträge in MySQL Datenbank finden « JimiHUY

  6. DELETE FROM a

    USING `db`.`tbl` a, `db`.`tbl` b
    WHERE b.Mail = a.Mail
    AND b.ID != a.ID

Hinterlasse eine Antwort

Pflichtfelder sind mit * markiert.


Schlagwörter: A/B-Test, Adapter, AddOn, Administration, Alühn, Alühn2, Amazon, Animation, Anonyme Klasse, Ant, Apache, API, Array, ArrayAccess, Attachment, Auftrag, Ausbildung, Auswertung, Authentifizierung, AutoLoader, AWS, Bedienung, Bedingung, Benchmark, Berechtigung, Berlin, Bildbearbeitung, Bildschirmfoto, Blog, BOM, Bot, Browser, Bugtracker, Byte Order Mark, Bücher, Cache, Call-Center, Callback, CamelCase, Canvas, Captcha, CDN, Cheatsheet, CLI, Clickout, Closure, Cloud, CodeSniffer, Collection, Community, Config, Contest, CouchDB, Countable, Cronjob, CSS, CSV, CustomLibrary, Custom_Model, Daemon, Data Mapper, Datei, Datenbank, Datenstruktur, Datentypen, Dating, Datum, Debug, Decorator, Dekorierer, Design, Design Patterns, Dokumentation, Dump, Duplikat, EC2, Eclipse, Email, Entwicklung, Entwurfsmuster, Enum, Erweiterung, Event, Eventhandling, Exception-Handling, Extension, Factory, Fallback, Fehler, Fehlermeldung, Filter, Firefox, Flash, Foreach, Formatierung, Formular, FTP, Funktion, Futon, ga:pi(), Getter, Google Analytics, Hash, Header, htaccess, HTML5, htpasswd, HTTP, IDE, If, Implementierung, InnoDB, Interceptor, Interface, Internet Explorer, isset, Iterator, Java, JavaScript, Job, jQuery, Kommentar, Konfiguration, Konsole, Kontrollstruktur, kostenlos, Kundenbetreuung, Late Static Binding, Links, Linux, Listeners, Lizenz, Logging, Löschen, Magento, Magic Methods, Manual, Marketing, Methode, Model, MVC, MySQL, NetBeans, Network, Nirvanix, Objekt, Observable, Observer, OOP, Open Source, Operator, OR-Mapper, Order, ORM, O’Reilly, Parameter, Partnersuche, Passwort, Performance, PHP, php.ini, PHP hates me, phpMyAdmin, PHPUnit, Plugin, Point and Click, Popup, Praktikum, Proxy, Prüfung, Qualitätssicherung, Query, Queue, Redesign, Refactoring, Reflection, Response, Rest-API, Rockstar, Rollback, S3, Samba, Scheifen, Schleife, Schutz, Screenshot, Secure Shell, Selbstreferenz, Server, Setter, setTimeout, Shop, Sicherheit, Sicherung, Sichtbarkeit, Singleton Pattern, Skin, SOAP, Social Network, Software, Sortierung, Sourcecode, Spam, Speicherproblem, Spickzettel, SPL, Splittest, SSH, Stammtisch, Statement, static, Statistik, Status, Stellvertreter, Strategy Pattern, Stream, String, Stuttgart, Stylesheet, Subversion, Sun VirtualBox, Support, SVN, Switch, Tag, Template, Template Method, Ternär Operator, Testing, Theme, Thumbnail, Tool, Tour, Tracking, Twitter, Type-Cast, Ubuntu, Umwandlung, Underscore, unset, Update, Upload, User Story, Validierung, Vererbung, Versionskontrolle, Versionsnummer, Verzweigung, Video, Videospiel, Virtualisierung, Vorschaubild, Warteschlange, Webserver, Webservice, Weiterleitung, Werkzeug, Windows, WindowsAzure, WordPress, Wrapper, Writer, XML, Youtube, Zeitschleife, Zeitsteuerung, Zend Framework, Zend_Application, Zend_Cloud, Zend_CodeGenerator, Zend_Http_Client, Zend_Reflection, Zend_Service, ZPress, Zugangskontrolle, Zugriffsmethode