🧚 ¡Oye! Beekeeper Studio es una GUI de bases de datos rápida, moderna y de código abierto Descarga
7 de enero de 2026 Por Matthew Rathbone

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 keepers es para MySQL, que de otro modo no deja que un DELETE lea 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 BY o ROW_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:

Documentación oficial:

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.

La versión de Beekeeper para Linux tiene el 100% de las funciones, sin recortes ni concesiones.

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."
— Alex K., desarrollador de bases de datos
★★★★★
"He probado muchas GUIs de bases de datos, pero Beekeeper logra el equilibrio perfecto entre funciones y simplicidad. Simplemente funciona."
— Sarah M., ingeniera full stack

¿Listo para mejorar tu flujo de trabajo con SQL?

download Descarga gratis