Eliminar todos los duplicados

8

Estoy tratando de eliminar todos los duplicados pero manteniendo un solo registro (identificación más corta). La siguiente consulta elimina los duplicados, pero requiere muchas iteraciones para eliminar todas las copias y conservar las originales.

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

Es MySQL.

Editar # 1 DDL

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

Editar # 2 Funcionó como un encanto liderado por @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
fuente

Respuestas:

8

Prueba esto:

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
)

Lo anterior funcionó para mi prueba de 50 correos electrónicos (5 correos electrónicos diferentes duplicados 10 veces).

Es posible que deba agregar un índice en la columna 'correo electrónico':

ALTER TABLE emailTable ADD INDEX ind_email (email);

Puede ser un poco lento para 250,000 filas. Fue lento para mí en una mesa que tenía 1.5 millones de filas (correctamente indexadas), y así es como se me ocurrió esta estrategia:

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

El beneficio de la tabla de memoria es que se utiliza un índice (clave primaria en minID) que acelera el proceso en una tabla temporal normal.

Derek Downey
fuente
4

Aquí hay un proceso de eliminación más ágil:

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;

Aquí hay algunos datos de muestra:

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;

Los corrí. Aquí están los resultados:

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>

Como se muestra, emailTable contendrá la primera aparición de cada dirección de correo electrónico y la identificación original correspondiente. Para este ejemplo:

CAVEAT: Respondí una pregunta similar a esta sobre la eliminación de la tabla mediante un enfoque de tabla temporal .

Darle una oportunidad !!!

RolandoMySQLDBA
fuente
Edité mis preguntas sobre la consulta que encontré funcionando. Aunque esa consulta es simple. Pero creo que técnicamente su solución es mejor si se hace en una mesa grande.
Gary Lindahl
2
La respuesta de @DTest es similar (usando una tabla externa) pero usa una tabla temporal MEMORY, cuyas claves se almacenan en el índice HASH en lugar de BTREE. Probablemente funcionaría más rápido. En cuanto al tamaño de los datos, siempre que haya suficiente RAM para acomodar las claves, es una buena solución. Buena, DTest.
RolandoMySQLDBA
2

Aquí hay una solución de Itzik realmente rápida. Esto funcionará en SQL 2005 y versiones posteriores.

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
fuente
OP está pidiendo MySQL
Derek Downey
2
Sí, me acabo de dar cuenta de eso; doh! Bueno, es una gran solución para MS SQL :)
Delux
No está mal saber acerca de MS SQL también: p, pero en este momento estoy buscando una solución MySQL.
Gary Lindahl