Encontrar registros duplicados

Los datos duplicados son un problema frecuente en bases de datos. Pueden aparecer por importaciones repetidas, errores en la aplicación, falta de restricciones de unicidad, o migraciones mal configuradas. Antes de poder eliminar duplicados, necesitas encontrarlos. MySQL ofrece varias técnicas para identificar registros duplicados basándose en una o más columnas.

Duplicados simples con GROUP BY y HAVING

La forma más directa de encontrar duplicados es agrupar por la columna sospechosa y filtrar los grupos con más de una fila:

SELECT email, COUNT(*) AS veces
FROM clientes
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY veces DESC;
emailveces
maria.garcia@email.com3
carlos.lopez@email.com2
info@empresa.com2

Esta consulta muestra los emails que aparecen más de una vez, junto con el número de ocurrencias. La cláusula HAVING filtra después de la agrupación, a diferencia de WHERE que filtra antes.

Ver los registros duplicados completos

La consulta anterior muestra qué valores están duplicados, pero no los registros completos. Para ver todos los datos de las filas duplicadas, combínala con un JOIN:

SELECT c.*
FROM clientes c
JOIN (
    SELECT email
    FROM clientes
    GROUP BY email
    HAVING COUNT(*) > 1
) dup ON c.email = dup.email
ORDER BY c.email, c.id;
idnombreemailfecha_registro
15María Garcíamaria.garcia@email.com2025-03-15
89Maria Garcia Lópezmaria.garcia@email.com2025-08-20
234María García L.maria.garcia@email.com2026-01-10
42Carlos Lópezcarlos.lopez@email.com2025-05-01
156Carlos López Martíncarlos.lopez@email.com2025-11-22

Ahora puedes ver los registros completos y decidir cuál conservar y cuáles eliminar.

Duplicados basados en múltiples columnas

A veces el duplicado se define por una combinación de columnas. Por ejemplo, un producto duplicado podría tener el mismo nombre y categoría:

SELECT nombre, categoria_id, COUNT(*) AS veces
FROM productos
GROUP BY nombre, categoria_id
HAVING COUNT(*) > 1;
nombrecategoria_idveces
Cargador USB-C82
Funda silicona83

Para ver los registros completos:

SELECT p.*
FROM productos p
JOIN (
    SELECT nombre, categoria_id
    FROM productos
    GROUP BY nombre, categoria_id
    HAVING COUNT(*) > 1
) dup ON p.nombre = dup.nombre AND p.categoria_id = dup.categoria_id
ORDER BY p.nombre, p.id;

Identificar el registro a conservar

Normalmente quieres conservar el registro más antiguo (menor ID) o el más reciente. Puedes identificar cuál es el "original" usando funciones de ventana:

SELECT
    id,
    nombre,
    email,
    fecha_registro,
    ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS num_fila
FROM clientes
WHERE email IN (
    SELECT email FROM clientes GROUP BY email HAVING COUNT(*) > 1
)
ORDER BY email, id;
idnombreemailfecha_registronum_fila
42Carlos Lópezcarlos.lopez@email.com2025-05-011
156Carlos López Martíncarlos.lopez@email.com2025-11-222
15María Garcíamaria.garcia@email.com2025-03-151
89Maria Garcia Lópezmaria.garcia@email.com2025-08-202
234María García L.maria.garcia@email.com2026-01-103

Las filas con num_fila = 1 son las que querrías conservar. Las demás son duplicados candidatos a eliminación.

Contar el total de duplicados

Para obtener una visión general del problema de duplicados:

SELECT
    COUNT(*) AS total_duplicados,
    SUM(veces - 1) AS filas_eliminables
FROM (
    SELECT COUNT(*) AS veces
    FROM clientes
    GROUP BY email
    HAVING COUNT(*) > 1
) sub;
total_duplicadosfilas_eliminables
34

Esto indica que hay 3 valores de email duplicados y que podrías eliminar 4 filas para quedarte con una de cada grupo.

Duplicados por similitud (casi duplicados)

A veces los duplicados no son exactos sino aproximados, como "Maria Garcia" y "María García". Puedes usar SOUNDEX o comparaciones más flexibles:

SELECT
    c1.id AS id1,
    c1.nombre AS nombre1,
    c2.id AS id2,
    c2.nombre AS nombre2,
    c1.email
FROM clientes c1
JOIN clientes c2 ON c1.email = c2.email AND c1.id < c2.id
ORDER BY c1.email;

La condición c1.id < c2.id evita duplicar las parejas (mostrar A-B y B-A) y evita comparar un registro consigo mismo.

Prevenir duplicados futuros

Una vez que limpies los duplicados, añade una restricción de unicidad para evitar que vuelvan a aparecer:

-- Crear índice único después de limpiar duplicados
ALTER TABLE clientes ADD UNIQUE INDEX uk_email (email);

Si intentas añadir el índice mientras hay duplicados, MySQL rechazará la operación. Primero limpia los duplicados y luego añade la restricción.

Practica con encontrar duplicados

Usa el editor para detectar valores repetidos con GROUP BY y HAVING:

Simulador SQL
Ctrl+Enter para ejecutar

Encontrar registros duplicados es el primer paso. En el siguiente artículo veremos cómo eliminarlos de forma segura manteniendo exactamente uno de cada grupo.

Escrito por Eduardo Lázaro