Alle Duplikate löschen

8

Ich versuche, alle Duplikate zu löschen, behalte aber nur einen einzigen Datensatz (kürzere ID). Die folgende Abfrage löscht Duplikate, benötigt jedoch viele Iterationen, um alle Kopien zu löschen und die Originalkopien beizubehalten.

DELETE FROM emailTable WHERE id IN (
 SELECT * FROM (
    SELECT id FROM emailTable GROUP BY email HAVING ( COUNT(email) > 1 )
 ) AS q
)

Es ist MySQL.

Bearbeiten Sie die DDL Nr. 1

CREATE TABLE `emailTable` (
 `id` mediumint(9) NOT NULL auto_increment,
 `email` varchar(200) NOT NULL default '',
 PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=298872 DEFAULT CHARSET=latin1

Edit # 2 Es funktionierte wie ein Zauber von @Dtest

DELETE FROM emailTable WHERE NOT EXISTS (
 SELECT * FROM (
    SELECT MIN(id) minID FROM emailTable    
    GROUP BY email HAVING COUNT(*) > 0
  ) AS q
  WHERE minID=id
)
Gary Lindahl
quelle

Antworten:

8

Versuche dies:

DELETE FROM emailTable WHERE NOT EXISTS (
 SELECT * FROM (
    SELECT MIN(id) minID FROM emailTable    
    GROUP BY email HAVING COUNT(*) > 0
  ) AS q
  WHERE minID=id
)

Das Obige funktionierte für meinen Test von 50 E-Mails (5 verschiedene E-Mails wurden 10 Mal dupliziert).

Möglicherweise müssen Sie einen Index in die Spalte "E-Mail" einfügen:

ALTER TABLE emailTable ADD INDEX ind_email (email);

Bei 250.000 Zeilen kann es etwas langsam sein. Bei einer Tabelle mit 1,5 Millionen Zeilen (richtig indiziert) war es für mich langsam. So kam ich zu dieser Strategie:

/* CREATE MEMORY TABLE TO HOUSE IDs of the MIN */
CREATE TABLE email_min (minID INT, PRIMARY KEY(minID)) ENGINE=Memory;

/* INSERT THE MINIMUM IDs */
INSERT INTO email_min SELECT id FROM email
    GROUP BY email HAVING MIN(id);

/* MAKE SURE YOU HAVE RIGHT INFO */
SELECT * FROM email 
 WHERE NOT EXISTS (SELECT * FROM email_min WHERE minID=id)

/* DELETE FROM EMAIL */
DELETE FROM email 
 WHERE NOT EXISTS (SELECT * FROM email_min WHERE minID=id)

/* IF ALL IS WELL, DROP MEMORY TABLE */
DROP TABLE email_min;

Der Vorteil der Speichertabelle besteht darin, dass ein Index verwendet wird (Primärschlüssel auf minID), der den Prozess gegenüber einer normalen temporären Tabelle beschleunigt.

Derek Downey
quelle
4

Hier ist ein optimierter Löschvorgang:

CREATE TABLE emailUnique LIKE emailTable;
ALTER TABLE emailUnique ADD UNIQUE INDEX (email);
INSERT IGNORE INTO emailUnique SELECT * FROM emailTable;
SELECT * FROM emailUnique;
ALTER TABLE emailTable  RENAME emailTable_old;
ALTER TABLE emailUnique RENAME emailTable;
DROP TABLE emailTable_old;

Hier einige Beispieldaten:

use test
DROP TABLE IF EXISTS emailTable;
CREATE TABLE `emailTable` (
 `id` mediumint(9) NOT NULL auto_increment,
 `email` varchar(200) NOT NULL default '',
 PRIMARY KEY  (`id`)
) ENGINE=MyISAM;
INSERT INTO emailTable (email) VALUES
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]'),
('[email protected]');
SELECT * FROM emailTable;

Ich habe sie geführt. Hier sind die Ergebnisse:

mysql> use test
Database changed
mysql> DROP TABLE IF EXISTS emailTable;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE `emailTable` (
    ->  `id` mediumint(9) NOT NULL auto_increment,
    ->  `email` varchar(200) NOT NULL default '',
    ->  PRIMARY KEY  (`id`)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)

mysql> INSERT INTO emailTable (email) VALUES
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
('[email protected]');
SELECT * FROM emailTable;
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]'),
    -> ('[email protected]');
Query OK, 15 rows affected (0.00 sec)
Records: 15  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM emailTable;
+----+----------------------------+
| id | email                      |
+----+----------------------------+
|  1 | redwards@gmail.com         |
|  2 | redwards@gmail.com         |
|  3 | redwards@gmail.com         |
|  4 | redwards@gmail.com         |
|  5 | rolandoedwards@gmail.com   |
|  6 | rolandoedwards@gmail.com   |
|  7 | rolandoedwards@gmail.com   |
|  8 | red@gmail.com              |
|  9 | red@gmail.com              |
| 10 | red@gmail.com              |
| 11 | rolandoedwards@gmail.com   |
| 12 | rolandoedwards@gmail.com   |
| 13 | rolandoedwards@comcast.net |
| 14 | rolandoedwards@comcast.net |
| 15 | rolandoedwards@comcast.net |
+----+----------------------------+
15 rows in set (0.00 sec)

mysql> CREATE TABLE emailUnique LIKE emailTable;
Query OK, 0 rows affected (0.04 sec)

mysql> ALTER TABLE emailUnique ADD UNIQUE INDEX (email);
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> INSERT IGNORE INTO emailUnique SELECT * FROM emailTable;
Query OK, 4 rows affected (0.01 sec)
Records: 15  Duplicates: 11  Warnings: 0

mysql> SELECT * FROM emailUnique;
+----+----------------------------+
| id | email                      |
+----+----------------------------+
|  1 | redwards@gmail.com         |
|  5 | rolandoedwards@gmail.com   |
|  8 | red@gmail.com              |
| 13 | rolandoedwards@comcast.net |
+----+----------------------------+
4 rows in set (0.00 sec)

mysql> ALTER TABLE emailTable  RENAME emailTable_old;
Query OK, 0 rows affected (0.03 sec)

mysql> ALTER TABLE emailUnique RENAME emailTable;
Query OK, 0 rows affected (0.00 sec)

mysql> DROP TABLE emailTable_old;
Query OK, 0 rows affected (0.00 sec)

mysql>

Wie gezeigt, enthält die emailTable das erste Vorkommen jeder E-Mail-Adresse und die entsprechende ursprüngliche ID. Für dieses Beispiel:

CAVEAT: Ich habe eine ähnliche Frage zum Löschen von Tabellen mithilfe eines temporären Tabellenansatzes beantwortet .

Versuche es !!!

RolandoMySQLDBA
quelle
Ich habe meine Fragen zu der Abfrage bearbeitet, die funktioniert hat. Obwohl diese Abfrage einfach ist. Aber ich denke, technisch gesehen ist Ihre Lösung besser, wenn sie auf einem großen Tisch durchgeführt werden soll?
Gary Lindahl
2
Die Antwort von @DTest ist ähnlich (unter Verwendung einer externen Tabelle), verwendet jedoch eine temporäre MEMORY-Tabelle, deren Schlüssel anstelle von BTREE im HASH-Index gespeichert sind. Es würde wahrscheinlich schneller funktionieren. In Bezug auf die Datengröße ist es eine gute Lösung, solange genügend RAM vorhanden ist, um die Schlüssel aufzunehmen. Schön, DTest.
RolandoMySQLDBA
2

Hier ist eine sehr schnelle Itzik-Lösung. Dies funktioniert in SQL 2005 und höher.

WITH Dups AS
(
  SELECT *,
    ROW_NUMBER()
      OVER(PARTITION BY email ORDER BY id) AS rn
  FROM dbo.emailTable
)
DELETE FROM Dups
WHERE rn > 1;
Delux
quelle
OP fragt nach MySQL
Derek Downey
2
Ja, habe das gerade gemerkt; doh! Nun, es ist eine großartige Lösung für MS SQL :)
Delux
Nicht schlecht über MS SQL zu wissen: p aber im Moment auf der Suche nach MySQL-Lösung.
Gary Lindahl