Eliminar registros duplicados

Una vez que has identificado los registros duplicados, el siguiente paso es eliminarlos conservando exactamente uno de cada grupo. Esta operación requiere cuidado porque un DELETE mal construido puede borrar todos los registros, incluido el que querías conservar. MySQL ofrece varias técnicas seguras para realizar esta limpieza.

Conservar el registro con menor ID

La estrategia más común es conservar el registro más antiguo (el que tiene el ID más bajo) y eliminar los demás. Usa un DELETE con subconsulta:

DELETE c1
FROM clientes c1
JOIN clientes c2
    ON c1.email = c2.email
    AND c1.id > c2.id;

Esta sentencia compara cada registro con todos los demás que tienen el mismo email. Solo elimina los registros cuyo ID es mayor que otro con el mismo email, lo que garantiza que el de menor ID sobrevive.

Antes de ejecutar el DELETE, verifica qué se va a eliminar cambiando DELETE por SELECT:

-- Previsualizar qué se eliminará
SELECT c1.*
FROM clientes c1
JOIN clientes c2
    ON c1.email = c2.email
    AND c1.id > c2.id;
idnombreemail
89Maria Garcia Lópezmaria.garcia@email.com
234María García L.maria.garcia@email.com
156Carlos López Martíncarlos.lopez@email.com

Conservar el registro más reciente

Si prefieres conservar el registro más reciente:

DELETE c1
FROM clientes c1
JOIN clientes c2
    ON c1.email = c2.email
    AND c1.id < c2.id;

Simplemente invertimos la comparación: ahora se eliminan los registros con ID menor, conservando el más alto.

Eliminar con ROW_NUMBER()

Las funciones de ventana ofrecen un enfoque más flexible:

DELETE FROM clientes
WHERE id IN (
    SELECT id FROM (
        SELECT id,
            ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
        FROM clientes
    ) ranked
    WHERE rn > 1
);

ROW_NUMBER() asigna un número secuencial a cada fila dentro de cada grupo de email. Las filas con rn > 1 son duplicados. La subconsulta intermedia es necesaria porque MySQL no permite referenciar directamente la tabla que se está modificando en una subconsulta del DELETE.

Duplicados basados en múltiples columnas

Cuando el duplicado se define por varias columnas, ajusta la condición del JOIN:

DELETE p1
FROM productos p1
JOIN productos p2
    ON p1.nombre = p2.nombre
    AND p1.categoria_id = p2.categoria_id
    AND p1.id > p2.id;

O con ROW_NUMBER():

DELETE FROM productos
WHERE id IN (
    SELECT id FROM (
        SELECT id,
            ROW_NUMBER() OVER (
                PARTITION BY nombre, categoria_id
                ORDER BY id
            ) AS rn
        FROM productos
    ) ranked
    WHERE rn > 1
);

Técnica con tabla temporal

Para mayor seguridad, puedes usar una tabla temporal como intermediaria:

-- 1. Crear tabla temporal con los IDs a conservar
CREATE TEMPORARY TABLE ids_conservar AS
SELECT MIN(id) AS id
FROM clientes
GROUP BY email;
 
-- 2. Eliminar todo lo que no esté en la lista
DELETE FROM clientes
WHERE id NOT IN (SELECT id FROM ids_conservar);
 
-- 3. Limpiar
DROP TEMPORARY TABLE ids_conservar;

Esta técnica es más explícita y fácil de verificar paso a paso. Puedes examinar la tabla temporal antes de ejecutar el DELETE para confirmar que contiene los IDs correctos.

Verificar después de la limpieza

Después de eliminar duplicados, verifica que no queden:

SELECT email, COUNT(*) AS veces
FROM clientes
GROUP BY email
HAVING COUNT(*) > 1;

Si la consulta no devuelve filas, la limpieza fue exitosa. Ahora es el momento de añadir una restricción para prevenir futuros duplicados:

ALTER TABLE clientes ADD UNIQUE INDEX uk_email (email);

Precauciones importantes

Siempre haz un backup antes de eliminar datos. Un DELETE masivo es una operación destructiva que no se puede deshacer fuera de una transacción.

Si usas InnoDB, envuelve la operación en una transacción para poder revertir si algo sale mal:

START TRANSACTION;
 
DELETE c1
FROM clientes c1
JOIN clientes c2
    ON c1.email = c2.email
    AND c1.id > c2.id;
 
-- Verificar el resultado
SELECT COUNT(*) FROM clientes;
 
-- Si todo es correcto
COMMIT;
 
-- Si algo salió mal
-- ROLLBACK;

Para tablas muy grandes con muchos duplicados, eliminar todo de golpe puede generar un gran uso de recursos. Considera eliminar en lotes:

-- Eliminar en lotes de 1000
DELETE c1
FROM clientes c1
JOIN clientes c2
    ON c1.email = c2.email
    AND c1.id > c2.id
LIMIT 1000;
 
-- Repetir hasta que no queden duplicados

La eliminación de duplicados es una operación de limpieza que idealmente solo necesitas hacer una vez. Después de limpiar, establece restricciones de unicidad para que la base de datos impida la entrada de duplicados en el futuro.

Escrito por Eduardo Lázaro