Eliminar duplicados en SQL significa una de dos cosas: ocultar las filas repetidas del resultado de una consulta con DISTINCT, o borrar las copias sobrantes de la tabla para que quede solo una de cada una. Los ejemplos funcionan en PostgreSQL, MySQL, SQLite y SQL Server.
Cómo eliminar duplicados en SQL
SELECT DISTINCT devuelve cada combinación de las columnas indicadas una sola vez y no toca la tabla. Para limpiar la tabla en sí, borra todas las filas menos una en cada grupo de duplicados, normalmente la que tiene el id más bajo:
SELECT DISTINCT column_list FROM table_name;
DELETE FROM table_name
WHERE id NOT IN (
SELECT id FROM (
SELECT MIN(id) AS id FROM table_name GROUP BY column_list
) AS keepers
);
Aquí Maria Lopez se registró tres veces:
-- Input: customers
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 3 | Maria Lopez | maria@example.com | 555-0142
-- 4 | Lena Novak | lena@example.com | 555-0121
-- 5 | Maria Lopez | maria@example.com | 555-0142
SELECT DISTINCT name, email, phone
FROM customers
ORDER BY name;
-- Output:
-- name | email | phone
-- ------------+-------------------+---------
-- Lena Novak | lena@example.com | 555-0121
-- Maria Lopez | maria@example.com | 555-0142
-- Sam Patel | sam@example.com | 555-0178
DELETE FROM customers
WHERE id NOT IN (
SELECT id FROM (
SELECT MIN(id) AS id FROM customers GROUP BY name, email, phone
) AS keepers
);
-- Output (customers after the DELETE):
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 4 | Lena Novak | lena@example.com | 555-0121
Reglas que debes conocer
-
Indica las columnas que hacen que dos filas sean duplicadas. Deja fuera el id y cualquier otra cosa que siempre sea diferente, como un timestamp
created_at. Las filas que difieren en cualquiera de las columnas indicadas se conservan todas. -
El envoltorio
SELECT id FROM (...) AS keeperses para MySQL, que de otro modo no deja que unDELETElea su propia tabla en una subconsulta: “You can’t specify target table ‘customers’ for update in FROM clause”. -
Revisa antes de borrar. Ejecuta primero la consulta con
GROUP BYoROW_NUMBER()para ver qué se va a eliminar, y haz el borrado dentro de una transacción que puedas revertir. - Evita nuevos duplicados con un índice único una vez que la tabla esté limpia:
CREATE UNIQUE INDEX customers_email_key ON customers (email);
INSERT INTO customers VALUES (6, 'Maria Lopez', 'maria@example.com', '555-0142');
-- ERROR: duplicate key value violates unique constraint "customers_email_key"
Sintaxis alternativa
En código de SQL Server es común ver que las copias se numeran en un CTE y se borran a través de él, algo que solo SQL Server permite. Sobre la tabla customers original:
-- SQL Server
WITH numbered AS (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY name, email, phone ORDER BY id
) AS row_num
FROM customers
)
DELETE FROM numbered WHERE row_num > 1;
-- Output (customers after the DELETE):
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 4 | Lena Novak | lena@example.com | 555-0121
Para combinar dos consultas sin repetir filas, usa UNION, que elimina los duplicados, en lugar de UNION ALL, que los conserva:
SELECT 'apple' AS fruit UNION ALL SELECT 'apple';
-- Output:
-- fruit
-- -----
-- apple
-- apple
SELECT 'apple' AS fruit UNION SELECT 'apple';
-- Output: apple
¿Cuándo deberías eliminar duplicados?
A veces solo necesitas ocultar las repeticiones en un resultado, y otras veces hay que limpiar la tabla en sí. Estos son los pasos habituales, desde revisar hasta borrar.
Mostrar cada valor una vez
Para un reporte, un menú desplegable o una revisión rápida de lo que hay en una columna, SELECT DISTINCT quita las repeticiones del resultado y deja la tabla como está.
Cuenta las copias antes de borrar
Agrupa por las columnas que definen un duplicado y quédate con los grupos que cumplan HAVING COUNT(*) > 1. Así ves qué está duplicado y cuántas veces, antes de eliminar nada. La guía para encontrar duplicados en SQL muestra más formas de buscarlos.
Elige qué copia conservar
ROW_NUMBER() numera las filas de cada grupo de duplicados. El ORDER BY dentro de OVER (...) decide qué fila recibe el 1 y se queda: la más antigua, la más reciente o la más completa.
Borra las copias sobrantes
Una vez que sepas qué fila conservar, borra las demás. Para conservar la fila más reciente en lugar de la más antigua, numera las filas por fecha, de la más nueva a la más vieja, y borra todo lo que tenga un número mayor que 1.
Ejemplos reales de cómo eliminar duplicados
Todos los países y paqueterías con los que haces envíos
DISTINCT se aplica a toda la fila de columnas seleccionadas, así que Canadá aparece una vez por cada paquetería:
-- Input: shipments
-- order_id | country | carrier
-- ---------+---------+--------
-- 1001 | Canada | UPS
-- 1002 | Germany | DHL
-- 1003 | Canada | FedEx
-- 1004 | Canada | UPS
-- 1005 | Germany | DHL
SELECT DISTINCT country, carrier
FROM shipments
ORDER BY country, carrier;
-- Output:
-- country | carrier
-- --------+--------
-- Canada | FedEx
-- Canada | UPS
-- Germany | DHL
Correos electrónicos que solo difieren en mayúsculas y minúsculas
Agrupa por el correo en minúsculas para que Jon@Example.com y jon@example.com cuenten como copias. (MySQL y SQL Server ya ignoran mayúsculas y minúsculas de forma predeterminada, y LOWER() no hace daño ahí.)
-- Input: accounts
-- id | email
-- ---+----------------------
-- 1 | Ana.Silva@example.com
-- 2 | jon@example.com
-- 3 | ana.silva@example.com
-- 4 | Jon@Example.com
-- 5 | mei@example.com
SELECT LOWER(email) AS email, COUNT(*) AS copies
FROM accounts
GROUP BY LOWER(email)
HAVING COUNT(*) > 1
ORDER BY email;
-- Output:
-- email | copies
-- ----------------------+-------
-- ana.silva@example.com | 2
-- jon@example.com | 2
Conserva la copia que tiene número de teléfono
Ordena primero las filas que tienen teléfono y luego por id, para que la fila más completa de cada grupo sea la número 1:
-- Input: contacts
-- id | email | phone
-- ---+------------------+---------
-- 1 | ravi@example.com | NULL
-- 2 | ravi@example.com | 555-0110
-- 3 | ines@example.com | 555-0133
-- 4 | ines@example.com | NULL
SELECT
id,
email,
phone,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY CASE WHEN phone IS NULL THEN 1 ELSE 0 END, id
) AS row_num
FROM contacts
ORDER BY email, row_num;
-- Output:
-- id | email | phone | row_num
-- ---+------------------+----------+--------
-- 3 | ines@example.com | 555-0133 | 1
-- 4 | ines@example.com | NULL | 2
-- 2 | ravi@example.com | 555-0110 | 1
-- 1 | ravi@example.com | NULL | 2
Al borrar las filas con número 2 o mayor (ids 1 y 4) queda un contacto completo por correo.
Borra copias exactas de una tabla sin id
Cuando las copias son idénticas en todas las columnas, no hay un id del cual conservar el más bajo. Copia las filas distintas a una tabla nueva y sustituye la original. Vuelve a agregar los índices y restricciones que tenía la tabla anterior, porque la copia no los conserva:
-- Input: page_views
-- url | viewed_at
-- ---------+--------------------
-- /pricing | 2026-10-01 09:00:00
-- /pricing | 2026-10-01 09:00:00
-- /docs | 2026-10-01 09:05:00
-- /pricing | 2026-10-01 09:00:00
-- In SQL Server, use SELECT DISTINCT * INTO page_views_clean FROM page_views,
-- then EXEC sp_rename 'page_views_clean', 'page_views'
CREATE TABLE page_views_clean AS SELECT DISTINCT * FROM page_views;
DROP TABLE page_views;
ALTER TABLE page_views_clean RENAME TO page_views;
-- Output (page_views afterwards):
-- url | viewed_at
-- ---------+--------------------
-- /pricing | 2026-10-01 09:00:00
-- /docs | 2026-10-01 09:05:00
Más información sobre cómo eliminar duplicados
En este blog:
- Guía para encontrar duplicados en SQL: más formas de detectar filas duplicadas antes de eliminarlas
- Detecta filas duplicadas en una tabla de SQL Server: el enfoque de SQL Server
- Guía de SQL COUNT DISTINCT: contar valores únicos en lugar de eliminar repeticiones
- Sintaxis y ejemplos de la sentencia DELETE de SQL: lo básico para borrar filas
- Cómo crear restricciones únicas en PostgreSQL: evita los duplicados para siempre
- Guía de CTE vs. subconsulta en SQL: las dos formas de escribir el paso de “qué filas conservar”
Documentación oficial:
- PostgreSQL: DELETE y funciones de ventana
- MySQL: DELETE y ROW_NUMBER()
- SQLite: DELETE y funciones de ventana
- SQL Server: DELETE y ROW_NUMBER
Beekeeper Studio es un cliente de bases de datos gratuito y de código abierto
La mejor herramienta de consultas y edición de SQL que he usado. Tiene todo lo que necesito para administrar mi base de datos. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio es rápido, intuitivo y fácil de usar. Es compatible con muchísimas bases de datos y funciona de maravilla en Windows, Mac y Linux.
Lo que dicen los usuarios de Beekeeper Studio
"Beekeeper Studio reemplazó por completo mi antiguo flujo de trabajo con SQL. Es rápido, intuitivo y hace que trabajar con bases de datos vuelva a ser agradable."
"He probado muchas GUIs de bases de datos, pero Beekeeper logra el equilibrio perfecto entre funciones y simplicidad. Simplemente funciona."