Encontrar y eliminar filas duplicadas en SQL
Detecta y elimina registros duplicados en tablas SQL usando GROUP BY y HAVING, conservando la fila canónica de forma segura.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión General
Las filas duplicadas se cuelan en las tablas por errores de aplicación, scripts de importación o condiciones de carrera. Desperdician espacio, distorsionan análisis y pueden romper restricciones únicas que pretendías aplicar. Encontrarlas requiere agrupar por las columnas que definen unicidad, y eliminarlas de forma segura significa conservar una fila canónica mientras se borran el resto sin perder datos relacionados.
Cuándo Usar
Usa este recurso cuando:
- Necesites identificar registros duplicados en una tabla.
- Una violación de restricción única impide agregar un índice requerido.
- Estés limpiando datos después de una importación o migración.
- Quieras deduplicar antes de aplicar una nueva clave primaria o índice único.
Solución
Encontrar duplicados en PostgreSQL
-- Encontrar emails duplicados en la tabla users
SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
-- Conservar la fila más antigua y eliminar el resto
WITH duplicates AS (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at) AS rn
FROM users
)
DELETE FROM users
WHERE id IN (
SELECT id FROM duplicates WHERE rn > 1
);
Explicación
La primera consulta agrupa filas por la columna que debería ser única y usa HAVING COUNT(*) > 1 para devolver solo duplicados. La segunda consulta usa una CTE con ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at). Cada grupo de duplicados se numera desde 1, y eliminamos todas las filas excepto la primera. La cláusula ORDER BY determina qué fila se conserva; aquí conservamos el registro más antiguo. Siempre ejecuta la versión SELECT de la CTE antes de DELETE para confirmar qué se eliminará.
Variantes
| Base de datos | Técnica | Notas |
|---|---|---|
| PostgreSQL | ROW_NUMBER() OVER | Flexible y segura |
| MySQL 8+ | ROW_NUMBER() OVER | Misma sintaxis que PostgreSQL |
| MySQL 5.7 | Self-join | Usa MIN(id) para conservar una fila |
| SQLite | DELETE con subconsulta IN | Funciona con window functions en 3.25+ |
Mejores Prácticas
- Previsualiza siempre antes de borrar. Ejecuta la CTE como
SELECTprimero para ver qué filas se conservarán. - Haz backup de la tabla o usa una transacción. Un mal
DELETEpuede eliminar miles de filas. - Elige la fila canónica con lógica de negocio. La más antigua, más reciente o más completa depende del caso de uso.
- Agrega una restricción única después de la limpieza. Esto evita que los duplicados vuelvan.
- Considera claves foráneas. Borrar una fila padre puede dejar huérfanas las filas hijas a menos que uses
ON DELETE CASCADEo actualices referencias primero.
Errores Comunes
- Borrar sin WHERE. Un
WHEREausente convierte la consulta en un borrado total de tabla. - Conservar la fila equivocada. Si ordenas aleatoriamente, puedes descartar el duplicado más valioso.
- Ignorar valores NULL.
NULLno es igual aNULL, por lo que duplicados con claves NULL pueden no detectarse conGROUP BY. - Ejecutar en producción durante tráfico alto. El bloqueo de contención puede bloquear escrituras; usa un enfoque por lotes o ventana de bajo tráfico.
- Olvidar actualizar secuencias relacionadas. Si borras el
idmás alto, puedes necesitar reiniciar una secuencia, aunque raramente es necesario.
Preguntas Frecuentes
P: ¿Qué pasa si los duplicados tienen diferentes valores en otras columnas? R: Elige la fila canónica por reglas de negocio, luego fusiona los datos o conserva la fila con los datos más completos o recientes.
P: ¿Puedo borrar duplicados en lotes?
R: Sí. Agrega AND id IN (SELECT id FROM duplicates WHERE rn > 1 LIMIT 1000) y ejecuta el borrado repetidamente hasta que no queden duplicados.
P: ¿Cómo evito que los duplicados vuelvan? R: Agrega una restricción única o índice único en las columnas que definen unicidad, y maneja excepciones de clave duplicada en tu aplicación.
Recursos Relacionados
Read Replicas — Scale Reads Without Changing Application Logic
A practical guide to read replicas: setting up replication, routing read queries, handling replication lag, and scaling read-heavy workloads with PostgreSQL, MySQL, and cloud-managed replicas.
GuideSQL CTEs — Common Table Expressions Explained
A practical guide to SQL Common Table Expressions (CTEs): non-recursive and recursive CTEs, readability, performance, and when to use them over subqueries.
DocDatabase Failover Runbook
A step-by-step runbook for executing database failover procedures safely with minimal downtime and data loss.
DocDatabase Schema Documentation Template
A template for documenting database schemas with entity relationships, field definitions, and migration history.
GuideFull-Text Search — Implement Search That Actually Works
A practical guide to full-text search: PostgreSQL tsvector, Elasticsearch indexing, query design, relevance tuning, and building search that users trust with autocomplete, faceting, and typo tolerance.