Saltar al contenido
LINWIND de Windows a Linux

~/diseno-y-web

Eliminar registros duplicados en MySQL, Adminer y consultas más rápidas

Marcos · · 5 min

Para eliminar registros duplicados en MySQL, localízalos primero con GROUP BY y HAVING COUNT(*) > 1, bórralos después con un DELETE que cruce la tabla consigo misma y deje solo la fila con el id más bajo de cada grupo, y termina creando un índice UNIQUE para que no vuelvan a colarse. Antes de nada, haz copia de la tabla. Las consultas de borrado valen también para MariaDB, que documenta la misma sintaxis de DELETE con varias tablas.

Para los ejemplos uso una tabla inventada, clientes, con una clave primaria id y una columna email que debería ser única y no lo es. Cambia los nombres por los tuyos.

Primero, la copia

Un DELETE confirmado no tiene vuelta atrás, así que copio siempre la tabla antes de tocarla. Con mysqldump, la utilidad de copias de MySQL, basta con indicar la base de datos y la tabla:

mysqldump -u usuario -p mi_base clientes > clientes.sql

Si no tienes acceso al terminal del servidor, Adminer también exporta tablas a SQL. Te lo cuento más abajo.

Encontrar los duplicados

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

Te devuelve cada dirección repetida y cuántas veces aparece. Si lo que se repite es una combinación de columnas, por ejemplo nombre y teléfono, pon las dos en el SELECT y en el GROUP BY.

Borrar los duplicados y quedarte con uno

Antes de borrar, mira qué se va a borrar. Esta consulta saca todas las filas que tienen otra con la misma dirección y un id menor, es decir, todas menos la primera de cada grupo:

SELECT c1.*
FROM clientes c1
INNER JOIN clientes c2
  ON c1.email = c2.email AND c1.id > c2.id;

Si el resultado es lo que esperabas, el borrado es la misma consulta convertida en un DELETE de varias tablas, la sintaxis que MySQL y MariaDB documentan para borrar a partir de un JOIN:

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

Si prefieres conservar el registro más reciente en vez del más antiguo, cambia > por <. Y lo hago así, cruzando la tabla consigo misma, porque el manual de MySQL no permite borrar de una tabla y seleccionar de esa misma tabla en una subconsulta. En MySQL, el típico DELETE ... WHERE id NOT IN (SELECT ...) sobre la misma tabla da error.

Ojo con los valores vacíos. En MySQL NULL no es igual a nada, ni siquiera a otro NULL, así que las filas sin dirección no se tocan con esta consulta. Si también te sobran, trátalas aparte con WHERE email IS NULL.

Si las filas son idénticas y no hay id

Cuando no hay clave primaria y las filas repetidas son copias exactas, el truco es el de la entrada original de 2009, con un cambio. Entonces lo hacía con CREATE TABLE ... SELECT * ... GROUP BY, y hoy MySQL rechaza por defecto ese GROUP BY porque el modo ONLY_FULL_GROUP_BY viene activado. Con DISTINCT funciona:

CREATE TABLE clientes_nueva LIKE clientes;
INSERT INTO clientes_nueva SELECT DISTINCT * FROM clientes;
RENAME TABLE clientes TO clientes_vieja, clientes_nueva TO clientes;

CREATE TABLE ... LIKE copia la estructura con sus índices, y el RENAME TABLE con dos cambios se hace de forma atómica, sin que ninguna otra conexión vea la tabla a medias. Hay una pega, y es que LIKE no copia las claves foráneas, así que si la tabla las tiene, usa el método anterior. Revisa clientes con calma y, solo cuando estés seguro, borra clientes_vieja con DROP TABLE.

Que no vuelvan a entrar

Con la tabla limpia, crea un índice único sobre la columna:

ALTER TABLE clientes ADD UNIQUE (email);

A partir de ahí, un INSERT con una dirección que ya existe da error de clave duplicada. Si quieres que la aplicación lo trague sin error, tienes dos opciones. INSERT IGNORE descarta la fila repetida y deja solo un aviso, e INSERT ... ON DUPLICATE KEY UPDATE actualiza la fila existente en lugar de insertar otra. Y recuerda lo del NULL, porque un índice UNIQUE admite varias filas con NULL en esa columna. Si no quieres ninguna, declárala también NOT NULL.

Adminer, por si no tienes phpMyAdmin

Todo lo anterior se puede lanzar desde Adminer, un gestor de bases de datos web que es un único archivo PHP. Lo subes al servidor, lo abres en el navegador y te conectas con el usuario y la contraseña de la base de datos. Funciona con MySQL, MariaDB, PostgreSQL, SQLite, MS SQL y Oracle, ejecuta cualquier consulta SQL y exporta tablas o bases enteras a SQL o CSV, que es la copia rápida que comentaba arriba. Necesita PHP 5.3 o posterior con las sesiones activadas.

Su propia web es clara con la seguridad. No deja conectarse a bases de datos sin contraseña y limita los intentos de acceso, pero recomienda no dejarlo accesible al público: limitarlo por IP, protegerlo con contraseña en el servidor web o, directamente, borrarlo cuando no lo necesites. Yo hago lo último. Lo subo por SFTP (tienes cómo en la guía de FTP online y clientes SFTP), hago lo que tenga que hacer y lo borro. Y si lo dejas, actualízalo cada vez que avise de una versión nueva, porque ha tenido fallos de seguridad en el pasado.

Consultas más rápidas

Los duplicados suelen aparecer junto a otro problema, las consultas lentas. Antes de tocar nada, pon EXPLAIN delante de la consulta. MySQL te enseña cómo piensa ejecutarla, qué índices usaría y cuántas filas espera recorrer. Con EXPLAIN ANALYZE la ejecuta de verdad y añade los tiempos reales, y en MariaDB lo equivalente es ANALYZE delante de la consulta.

Si ves que recorre la tabla entera, casi siempre falta un índice en las columnas del WHERE o del JOIN. Los índices de varias columnas solo sirven cuando la consulta usa sus columnas empezando por la izquierda. Un índice sobre (apellido, nombre) ayuda a buscar por apellido o por apellido y nombre, pero no solo por nombre.

Para saber qué consultas mirar, activa el registro de consultas lentas. Viene apagado por defecto, se enciende con la variable slow_query_log y guarda las consultas que tardan más de long_query_time segundos, 10 por defecto. Yo lo bajaría a uno o dos segundos durante unos días para ver qué sale.

Si la base de datos está detrás de una web lenta, en la guía para medir y mejorar la velocidad de carga de una web tienes el resto de cosas que mirar.

Revisado y actualizado en octubre de 2026.

# sigue leyendo

7 comentarios

  1. La verdad que de todas las opciones que he mirado, la tuya es la mas sencilla, rapida y completa.

    Solucionaste mi problema, muchisimas gracias!

  2. Otra forma sin drop table o eliminar tablas.

    Antes de nada efectuar un respaldo de DB y mantenerla en lugar seguro y localizada.
    Accedemos a la base de datos, seleccionamos DB y efectuamos respaldo con exportar (pestaña superior) elegimos todas las tablas y procedemos, ya tenemos un respaldo de la DB actualizado que no está nunca de más.
    PASOS PARA ELIMINAR CONTENIDO DUPLICADO EN MYSQL
    Ahora que ya tenemos el respaldo ya se pueden fiar más de lo que vamos a hacer.
    Seguimos en la DB, esta vez punteamos el comando SQL ventana superior y por este orden ejecutamos las siguientes consultas en la ventana editora de texto:
    1.
    CREATE TABLE `pon el nombre de tu tabla aquí_new` as
    SELECT * FROM `pon el nombre de tu tabla aquí` WHERE 1 GROUP BY `title`;
    2.
    RENAME TABLE tu_tabla TO tu_tabla_bak;
    3.
    RENAME TABLE tu_tabla_new TO tu_tabla;

    No olvides continuar en cada paso (bajo editor derecha)

    Explicación comandos: con el paso 1 creamos una nueva tabla desde la tabla que contiene el contenido, para aquellos que no sepan su nombre lo normal es que sea la que más pesa, con _new diferenciamos la original de la nueva o si lo prefieres pon _prohibido_fumar jeje.
    Copia y pega tal cual las dos lineas con el nombre de tu tabla. La segunda linea no marca la fuente, osea la tabla original a copiar, lo que le decimos con WHERE… es que ordene la nueva tabla por título eliminándose los registros duplicados, se puede hacer sustituyendo title por cualquier otra función del Order By (id,alias,fulltext….) ver tuto mysql.

    Con el paso 2 hacemos un nuevo respaldo a la tabla original, por si acaso no nos gusta el resultado y así basta con volver a renombrar a la inversa para recuperar la tabla original y de paso evitamos conflictos del mismo nombre.

    Con el paso tres copias la nueva tabla a tu tabla original y listo, espero que les sirva si tienen gran cantidad de artículos, para poco contenido no merece la pena.
    Un saludo de Juan Carlos.

  3. Si pero tienes una alternativa mucho mas fácil

    ALTER IGNORE TABLE tabla ADD UNIQUE INDEX(campo);
    esto te borra todos los registros duplicados

    y si no queres que sea obligatoria cambias ejecutas esto
    ALTER TABLE tabla DROP INDEX `campo`;

    🙂

  4. Hola…

    Funcionó de maravilla… Muchas gracias…

    Ahora te pregunto….

    Puedo hacer un script con las 3 instrucciones para que se ejecuten una tras otra automaticamente con phpmyadmin, ya se los nombres de las tablas, simpre son las mismas (lo he hecho una a una con phpmyadmin)….

    Es para eliminar post duplicados en wordpress de varios blogs..

    Gracias

  5. Si pero por ejemplo ustedes explican lo mismo de siempre pero no dicen, po rejemplo, en campo que lleve text no podras poner indixes por que mysql no lo permite y hablo de campos donde son mas usados como para meter redacciones o descripciones largas de un articulo.

    lo mejor y mas viable es usar esta sentencia
    DELETE FROM mitabla
    WHERE campoid NOT IN (SELECT *
    FROM (SELECT MIN(n.campoid)
    FROM mitabla n
    GROUP BY n.micampo) x)

Deja un comentario

Tu correo no se publica.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.