Excluindo todas as duplicatas

8

Estou tentando excluir todas as duplicatas, mas mantendo apenas um registro (identificação mais curta). A consulta a seguir exclui duplicatas, mas leva muitas iterações para excluir todas as cópias e manter as originais.

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

É MySQL.

Editar DDL nº 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 Funcionou como um encanto 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
fonte

Respostas:

8

Tente o seguinte:

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
)

O exemplo acima funcionou para o meu teste de 50 e-mails (5 e-mails diferentes duplicados 10 vezes).

Pode ser necessário adicionar um índice na coluna 'email':

ALTER TABLE emailTable ADD INDEX ind_email (email);

Pode ser um pouco lento para 250.000 linhas. Foi lento para mim em uma tabela com 1,5 milhão de linhas (indexada corretamente), e foi assim que surgiu essa estratégia:

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

O benefício da tabela de memória é que existe um índice usado (chave primária no minID) que acelera o processo em uma tabela temporária normal.

Derek Downey
fonte
4

Aqui está um processo de exclusão mais simplificado:

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;

Aqui estão alguns dados de exemplo:

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;

Eu os corri. Aqui estão os 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>

Conforme mostrado, a emailTable conterá a primeira ocorrência de cada endereço de email e o ID original correspondente. Para este exemplo:

CAVEAT: Respondi a uma pergunta semelhante a essa sobre exclusão de tabela por meio de uma abordagem de tabela temporária .

De uma chance !!!

RolandoMySQLDBA
fonte
Editei minhas perguntas sobre a consulta que encontrei funcionando. Embora essa consulta seja simples. Mas acho que tecnicamente sua solução é melhor se for feita em uma mesa grande?
Gary Lindahl
2
A resposta do @DTest é semelhante (usando uma tabela externa), mas usa uma tabela temporária de MEMORY, cujas chaves são armazenadas no índice HASH em vez de BTREE. Provavelmente funcionaria mais rápido. Quanto ao tamanho dos dados, desde que haja RAM suficiente para acomodar as chaves, é uma boa solução. Bom, DTest.
RolandoMySQLDBA
2

Aqui está uma solução rápida e real do Itzik. Isso funcionará no SQL 2005 e superior.

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
fonte
OP está pedindo MySQL
Derek Downey
2
Sim, acabei de perceber isso; doh! Bem, é uma grande solução para MS SQL :)
Delux
Não é ruim saber sobre o MS SQL também: p, mas no momento procurando a solução MySQL.
precisa saber é o seguinte