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

Para encontrar duplicados en SQL, agrupa las filas por las columnas que deberían ser únicas y quédate con los grupos que tengan más de una fila. Luego, las funciones de ventana y los self-joins te muestran las filas duplicadas en sí, para que decidas qué copia conservar. Los ejemplos funcionan en PostgreSQL, MySQL, SQLite y SQL Server.

Cómo encontrar duplicados en SQL

GROUP BY junta las filas con el mismo valor, COUNT(*) cuenta cada grupo y HAVING se queda con los grupos que tienen más de una fila:

SELECT columns, COUNT(*)
FROM table_name
GROUP BY columns
HAVING COUNT(*) > 1;

Por ejemplo, para encontrar direcciones de correo que usa más de un cliente:

-- Input: customers
-- id | name           | email
-- ---+----------------+----------------------
-- 1  | Maria Lopez    | maria.lopez@gmail.com
-- 2  | Sam Patel      | sam@patel.dev
-- 3  | M. Lopez       | maria.lopez@gmail.com
-- 4  | Kenji Watanabe | kenji@watanabe.jp

SELECT email, COUNT(*) AS copies
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;

-- Output:
-- email                 | copies
-- ----------------------+-------
-- maria.lopez@gmail.com | 2

Para considerar filas como duplicadas solo cuando coinciden varias columnas a la vez, incluye cada una de ellas tanto en SELECT como en GROUP BY.

Reglas que debes conocer

  • Cada columna que selecciones debe estar en GROUP BY o dentro de un agregado. Agregar name a la consulta anterior no muestra los nombres de los duplicados. Da un error, excepto en SQLite, que muestra un nombre por correo:
-- Input: the customers table above

SELECT name, email, COUNT(*) AS copies
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;

-- ERROR: column "customers.name" must appear in the GROUP BY clause or be used in an aggregate function

Para ver cada fila duplicada, usa una de las consultas de “Sintaxis alternativa”.

  • Los NULL se agrupan juntos y las mayúsculas importan. Todos los correos NULL caen en un mismo grupo, así que parecen duplicados entre sí, mientras que Ana@Silva.dev y ana@silva.dev no:
-- Input: users
-- id | email
-- ---+--------------
-- 1  | ana@silva.dev
-- 2  | NULL
-- 3  | Ana@Silva.dev
-- 4  | NULL

SELECT email, COUNT(*) AS copies
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

-- Output:
-- email | copies
-- ------+-------
-- NULL  | 2

-- MySQL and SQL Server ignore case by default, so they also return ana@silva.dev | 2

-- Skip the NULLs and ignore case:
SELECT LOWER(email) AS email, COUNT(*) AS copies
FROM users
WHERE email IS NOT NULL
GROUP BY LOWER(email)
HAVING COUNT(*) > 1;

-- Output:
-- email         | copies
-- --------------+-------
-- ana@silva.dev | 2

Sintaxis alternativa

GROUP BY devuelve una fila por cada valor duplicado. Para obtener las filas duplicadas en sí, mejor cuenta sobre una ventana. COUNT(*) OVER (PARTITION BY email) pone el conteo de cada correo en todas las filas que lo tienen:

-- Input: the customers table above

SELECT id, name, email, copies
FROM (
  SELECT id, name, email, COUNT(*) OVER (PARTITION BY email) AS copies
  FROM customers
) AS c
WHERE copies > 1
ORDER BY id;

-- Output:
-- id | name        | email                 | copies
-- ---+-------------+-----------------------+-------
-- 1  | Maria Lopez | maria.lopez@gmail.com | 2
-- 3  | M. Lopez    | maria.lopez@gmail.com | 2

EXISTS hace lo mismo sin una función de ventana: conserva cada fila para la que existe otra fila con el mismo correo:

-- Input: the customers table above

SELECT id, name, email
FROM customers c
WHERE EXISTS (
  SELECT 1 FROM customers other
  WHERE other.email = c.email AND other.id <> c.id
);

-- Output:
-- id | name        | email
-- ---+-------------+----------------------
-- 1  | Maria Lopez | maria.lopez@gmail.com
-- 3  | M. Lopez    | maria.lopez@gmail.com

¿Cuándo deberías buscar duplicados?

Los duplicados se cuelan por importaciones, doble clic, reintentos y restricciones faltantes. Estas son las cuatro tareas que más seguido vas a necesitar hacer con ellos.

Verifica que una columna sea única

Antes de agregar una restricción UNIQUE, o cuando un inicio de sesión o una búsqueda empieza a devolver dos filas, lista los valores que aparecen más de una vez. GROUP BY con HAVING responde esto directamente.

Ve todas las filas involucradas

Saber qué valor se repite no basta para arreglarlo: necesitas las filas completas para ver en qué se diferencian y cuál es la correcta. Usa los valores duplicados en otra consulta sobre la misma tabla.

Marca las copias sobrantes

Para limpiar, conserva una fila de cada grupo de duplicados y elimina el resto. ROW_NUMBER() numera las filas dentro de cada grupo, así que todo lo que venga después de la fila 1 es una copia sobrante.

Detecta envíos dobles

Un botón con doble clic o un pago reintentado deja dos filas que coinciden en todo menos en su ID. Unir la tabla consigo misma las empareja, y t1.id < t2.id lista cada par una sola vez.

Ejemplos reales para encontrar duplicados

Encuentra habitaciones de hotel reservadas dos veces

Una habitación solo puede reservarse una vez por noche, así que un duplicado es la misma habitación en la misma noche. Agrupa por ambas columnas:

-- Input: bookings
-- booking_id | room | night      | guest
-- -----------+------+------------+----------
-- 1          | 204  | 2026-11-06 | R. Okafor
-- 2          | 204  | 2026-11-07 | R. Okafor
-- 3          | 310  | 2026-11-06 | L. Chen
-- 4          | 204  | 2026-11-06 | P. Novak

SELECT room, night, COUNT(*) AS bookings
FROM bookings
GROUP BY room, night
HAVING COUNT(*) > 1;

-- Output:
-- room | night      | bookings
-- -----+------------+---------
-- 204  | 2026-11-06 | 2

Lista las cuentas que comparten un correo

Hay gente que se registra dos veces con la misma dirección escrita con distintas mayúsculas. Particiona por LOWER(email) para poner ambas cuentas lado a lado:

-- Input: accounts
-- id | username | email
-- ---+----------+----------------
-- 1  | jkim     | jin@kim.kr
-- 2  | olivia_r | olivia@reyes.mx
-- 3  | jin.kim  | Jin@Kim.kr
-- 4  | noah_b   | noah@baker.uk

SELECT id, username, email, accounts_with_email
FROM (
  SELECT id, username, email,
         COUNT(*) OVER (PARTITION BY LOWER(email)) AS accounts_with_email
  FROM accounts
) AS a
WHERE accounts_with_email > 1
ORDER BY id;

-- Output:
-- id | username | email      | accounts_with_email
-- ---+----------+------------+--------------------
-- 1  | jkim     | jin@kim.kr | 2
-- 3  | jin.kim  | Jin@Kim.kr | 2

Marca personas duplicadas antes de una limpieza

Trata a los usuarios con el mismo nombre y apellido como una sola persona, conserva el registro más antiguo y etiqueta los demás. Un CTE separa la numeración del etiquetado:

-- Input: users
-- id | first_name | last_name | email
-- ---+------------+-----------+----------------
-- 1  | Priya      | Shah      | priya@shah.net
-- 2  | Tom        | Becker    | tom@becker.de
-- 3  | Priya      | Shah      | pshah@work.com
-- 4  | Lena       | Kowalski  | lena@kowal.pl
-- 5  | Tom        | Becker    | tbecker@mail.de

WITH numbered AS (
  SELECT id, first_name, last_name, email,
         ROW_NUMBER() OVER (PARTITION BY first_name, last_name ORDER BY id) AS copy_number
  FROM users
)
SELECT id, first_name, last_name, email,
       CASE WHEN copy_number > 1 THEN 'Duplicate' ELSE 'Original' END AS status
FROM numbered
ORDER BY id;

-- Output:
-- id | first_name | last_name | email           | status
-- ---+------------+-----------+-----------------+----------
-- 1  | Priya      | Shah      | priya@shah.net  | Original
-- 2  | Tom        | Becker    | tom@becker.de   | Original
-- 3  | Priya      | Shah      | pshah@work.com  | Duplicate
-- 4  | Lena       | Kowalski  | lena@kowal.pl   | Original
-- 5  | Tom        | Becker    | tbecker@mail.de | Duplicate

Las filas marcadas como Duplicate son las que hay que fusionar o eliminar. La guía para eliminar duplicados te muestra cómo borrarlas.

Encuentra pedidos hechos dos veces por error

Dos pedidos del mismo cliente, del mismo producto y el mismo día probablemente son un solo pedido enviado dos veces:

-- Input: orders
-- order_id | customer_id | product   | order_date
-- ---------+-------------+-----------+-----------
-- 1001     | 42          | Desk Lamp | 2026-03-02
-- 1002     | 42          | Desk Lamp | 2026-03-02
-- 1003     | 42          | Desk Lamp | 2026-03-09
-- 1004     | 57          | Desk Lamp | 2026-03-02

SELECT o1.order_id AS first_order, o2.order_id AS repeat_order,
       o1.customer_id, o1.product
FROM orders o1
JOIN orders o2
  ON o1.customer_id = o2.customer_id
 AND o1.product = o2.product
 AND o1.order_date = o2.order_date
 AND o1.order_id < o2.order_id;

-- Output:
-- first_order | repeat_order | customer_id | product
-- ------------+--------------+-------------+----------
-- 1001        | 1002         | 42          | Desk Lamp

Más lecturas sobre 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 GUI 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