La eliminación de duplicados en las tablas de MySQL es un problema común, que generalmente es el resultado de una restricción que falta para evitar esos duplicados de antemano. Pero este problema común generalmente viene con necesidades específicas ... que requieren enfoques específicos. El enfoque debe ser diferente según, por ejemplo, el tamaño de los datos, la entrada duplicada que se debe mantener (generalmente la primera o la última), si hay índices que se deben mantener o si queremos realizar cualquier otro acción sobre los datos duplicados.
También hay algunas especificidades en MySQL en sí, como no poder hacer referencia a la misma tabla en una causa FROM al realizar una ACTUALIZACIÓN de tabla (generará el error de MySQL # 1093). Esta limitación se puede superar mediante el uso de una consulta interna con una tabla temporal (como se sugiere en algunos enfoques anteriores). Pero esta consulta interna no funcionará especialmente bien cuando se trata con grandes fuentes de datos.
Sin embargo, existe un mejor enfoque para eliminar duplicados, que es eficiente y confiable, y que puede adaptarse fácilmente a diferentes necesidades.
La idea general es crear una nueva tabla temporal, generalmente agregando una restricción única para evitar más duplicados, e INSERTAR los datos de su tabla anterior en la nueva, mientras se ocupan de los duplicados. Este enfoque se basa en consultas simples de INSERT de MySQL, crea una nueva restricción para evitar más duplicados y omite la necesidad de utilizar una consulta interna para buscar duplicados y una tabla temporal que debe mantenerse en la memoria (por lo tanto, también se ajustan a grandes fuentes de datos).
Así es como se puede lograr. Dado que tenemos una tabla de empleados , con las siguientes columnas:
employee (id, first_name, last_name, start_date, ssn)
Para eliminar las filas con una columna ssn duplicada y mantener solo la primera entrada encontrada, se puede seguir el siguiente proceso:
-- 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;
Explicación técnica
- La línea # 1 crea una nueva tabla tmp_eployee con exactamente la misma estructura que la tabla de empleados
- La línea # 2 agrega una restricción ÚNICA a la nueva tabla tmp_eployee para evitar más duplicados
- La línea n. ° 3 explora la tabla de empleados original por id, insertando nuevas entradas de empleados en la nueva tabla tmp_eployee , mientras ignora las entradas duplicadas
- La línea n. ° 4 cambia el nombre de las tablas, de modo que la nueva tabla de empleados contiene todas las entradas sin los duplicados, y una copia de seguridad de los datos anteriores se mantiene en la tabla backup_employee
⇒ Usando este enfoque, 1.6M registros se convirtieron en 6k en menos de 200s.
Chetan , siguiendo este proceso, puede eliminar rápida y fácilmente todos sus duplicados y crear una restricción ÚNICA ejecutando:
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;
Por supuesto, este proceso puede modificarse aún más para adaptarlo a diferentes necesidades al eliminar duplicados. Algunos ejemplos siguen.
✔ Variación para mantener la última entrada en lugar de la primera
A veces necesitamos mantener la última entrada duplicada en lugar de la primera.
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;
- En la línea # 3, la cláusula ORDER BY id DESC hace que las últimas ID tengan prioridad sobre el resto
✔ Variación para realizar algunas tareas en los duplicados, por ejemplo, llevar un recuento de los duplicados encontrados
A veces necesitamos realizar un procesamiento adicional en las entradas duplicadas que se encuentran (como mantener un recuento de los duplicados).
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;
- En la línea # 3, se crea una nueva columna n_duplicates
- En la línea # 4, la consulta INSERT INTO ... ON DUPLICATE KEY UPDATE se usa para realizar una actualización adicional cuando se encuentra un duplicado (en este caso, aumentar un contador) La consulta INSERT INTO ... ON DUPLICATE KEY UPDATE puede ser Se utiliza para realizar diferentes tipos de actualizaciones para los duplicados encontrados.
✔ Variación para regenerar la identificación de campo incremental automático
A veces usamos un campo de incremento automático y, para mantener el índice lo más compacto posible, podemos aprovechar la eliminación de los duplicados para regenerar el campo de incremento automático en la nueva tabla temporal.
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;
- En la línea n. ° 3, en lugar de seleccionar todos los campos de la tabla, se omite el campo id para que el motor DB genere uno nuevo automáticamente
✔ Otras variaciones
Muchas modificaciones adicionales también son factibles dependiendo del comportamiento deseado. Como ejemplo, las siguientes consultas utilizarán una segunda tabla temporal para, además de 1) mantener la última entrada en lugar de la primera; y 2) aumentar un contador en los duplicados encontrados; también 3) regenere la identificación de campo incremental automático mientras mantiene el orden de entrada como estaba en los datos anteriores.
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;