Borrar duplicados en SQL

Borrar correos duplicados en SQL

Borrar duplicados en SQL

Estaba revisando hilos viejos en Reddit —como uno hace cuando quiere ver cómo piensa la comunidad— y me encontré con un DELETE que parecía correcto a primera vista. Un usuario mostraba una consulta para borrar correos duplicados en SQL dejando el id mínimo, y la respuesta que recibió tenía un detalle que pocos notaron y que podía terminar borrando más de lo debido.

En SQLenVivo hacemos lo de siempre: reproducimos el caso, lo ejecutamos, lo observamos y lo corregimos. Nada de asumir ni de confiar ciegamente en un snippet de internet. Seguidamente explico qué hacía exactamente ese DELETE, por qué funcionaba de manera incorrecta y cómo reescribirlo para que realmente cumpla con lo que promete: borrar correos duplicados en SQL dejando solo el registro con el id mínimo por email.


Creación de la tabla de prueba

Para este ejemplo trabajaremos con una tabla real y no con un CTE. La razón es simple: cuando ejecutamos un DELETE, necesitamos persistencia. Un CTE es efímero: vive solo durante la consulta y no permite observar el antes y el después. En cambio, una tabla nos deja insertar datos, ejecutar el DELETE, revisar el resultado y confirmar qué ocurrió.

create table T (
    id int identity(1,1) primary key,
    email varchar(100)
);
go

Insertar datos con correos repetidos y únicos

insert into T (email) values
('a@sqlenvivo.com'),
('b@sqlenvivo.com'),
('a@sqlenvivo.com'),
('c@sqlenvivo.com'),
('b@sqlenvivo.com'),
('d@sqlenvivo.com');
go

El DELETE sospechoso del foro usado para borrar duplicados en SQL.

delete from t
where id not in (
    select min(id)
    from t
    group by email
    having count(1) > 1
);

Ese HAVING count(1) > 1 parece inocente, pero introduce una condición que cambia el comportamiento del DELETE. Al filtrar los correos que aparecen más de una vez, la subconsulta excluye todos los correos únicos. Y aquí es donde el problema se vuelve evidente: si un correo aparece una sola vez, su id no estará en el NOT IN. En consecuencia, el DELETE lo eliminará.

Este enfoque solo funciona cuando la tabla contiene exclusivamente correos repetidos; si solo hay elementos únicos, la consulta elimina registros que deberían conservarse. Este es el tipo de detalle que pasa desapercibido en un hilo de Reddit, y que en producción puede convertirse en un desastre.

No es lo que queremos.


Borrar duplicados en SQL: El DELETE correcto usando GROUP BY

delete from t
where id not in (
    select min(id)
    from t
    group by email
);

Esto sí cumple el enunciado

  • Cada correo, esté repetido o no, aporta su min(id)
  • Solo se borran los duplicados
  • Se conserva un solo registro por email

Borrar duplicados en SQL: La versión moderna con ROW_NUMBER()

with d as (
    select
        id,
        row_number() over (partition by email order by id) as rn
    from t
)
delete from d
where rn > 1;

Aquí no hay dudas: rn = 1 se queda y rn > 1 se va. El comportamiento es el que esperamos. Nada de interpretaciones ambiguas ni efectos colaterales: este patrón es explícito, legible y robusto; por esto el uso de funciones de ventana es la forma moderna de optimizar consultas en SQL.


Con esto cerramos el artículo: vimos el problema, lo reproducimos, lo analizamos y lo corregimos con dos enfoques distintos.

Si quieres revisar el código, la solución está disponible en el repositorio, con comentarios en inglés y en español para que puedas seguir cada paso. También te invito a pasar por este artículo donde explico los problemas de trabajar con valores NULL y cómo afecta a las consultas.

Por fortuna, en este problema de eliminación, el enunciado indicaba que los id no podían ser NULL.


Imagen de Geri ClevelandPixabay

error: Content is protected !!