

- Description
- Deux maisons en brique quasi identiques dans une rangée : des doublons presque parfaits.
- Source
- Smallbones · page d’origine
- Licence
- CC0 Domaine public
Dédoublonnage
Garder une seule ligne par clé métier : un classement par groupe, puis une suppression.
Énoncé#
Dans dbo.Contacts, conserver un seul contact par adresse e-mail : le plus récent ; à date égale, celui dont l'identifiant est le plus élevé.
Jeu de données#
CREATE TABLE dbo.Contacts
(
ContactId int NOT NULL PRIMARY KEY,
Email nvarchar(320) NOT NULL,
DateCreation datetime2(0) NOT NULL
);
INSERT INTO dbo.Contacts (ContactId, Email, DateCreation)
VALUES (1, N'a@exemple.org', '2026-01-01'),
(2, N'a@exemple.org', '2026-02-01'),
(3, N'b@exemple.org', '2026-01-15'),
(4, N'a@exemple.org', '2026-02-01'); -- même date que le contact 2
Procédural contre ensembliste#
Procédural#
DECLARE @Email nvarchar(320);
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
SELECT Email FROM dbo.Contacts GROUP BY Email HAVING COUNT(*) > 1;
OPEN c;
FETCH NEXT FROM c INTO @Email;
WHILE @@FETCH_STATUS = 0
BEGIN
DELETE FROM dbo.Contacts
WHERE Email = @Email
AND ContactId <> (SELECT TOP (1) ContactId
FROM dbo.Contacts
WHERE Email = @Email
ORDER BY DateCreation DESC, ContactId DESC);
FETCH NEXT FROM c INTO @Email;
END;
CLOSE c;
DEALLOCATE c;Ensembliste#
WITH D AS
(
SELECT ContactId, Email, DateCreation,
ROW_NUMBER() OVER (PARTITION BY Email
ORDER BY DateCreation DESC, ContactId DESC) AS Rang
FROM dbo.Contacts
)
DELETE FROM D
WHERE Rang > 1;Résultat attendu#
Restent les contacts 3 et 4 : pour a@exemple.org, le plus récent est conservé, et l'égalité de date est départagée par l'identifiant le plus élevé (contact 4).
Pourquoi l'ensembliste est préférable#
- Le
ROW_NUMBERattribue un rang à chaque ligne au sein de son groupe en un seul passage ; supprimer les rangs supérieurs à 1 supprime tous les doublons en une instruction. - L'ordre
DateCreation DESC, ContactId DESCest unique : le résultat est déterministe, même avec des ex æquo. - La version procédurale relance, pour chaque adresse en double, une recherche et une suppression.
Pièges#
- Sans clé de départage, l'ordre entre ex æquo est arbitraire : deux exécutions peuvent conserver des lignes différentes.
- Définition du doublon : la comparaison suit la collation de la colonne (insensible à la casse par défaut dans la plupart des cas). Si les adresses contiennent des espaces superflus, normaliser dans le
PARTITION BY(LOWER(LTRIM(RTRIM(Email)))). - Supprimer, c'est irréversible : commencer par un
SELECTdes lignes de rang > 1, archiver avecOUTPUT, travailler dans une transaction. - Empêcher le retour des doublons par une contrainte :
CREATE UNIQUE INDEX UX_Contacts_Email ON dbo.Contacts (Email);
Variantes#
-- 1) Diagnostic avant suppression
WITH D AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY Email ORDER BY DateCreation DESC, ContactId DESC) AS Rang FROM dbo.Contacts)
SELECT * FROM D WHERE Rang > 1;
-- 2) Suppression avec archivage des lignes supprimées
WITH D AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY Email ORDER BY DateCreation DESC, ContactId DESC) AS Rang FROM dbo.Contacts)
DELETE FROM D
OUTPUT deleted.ContactId, deleted.Email, deleted.DateCreation
INTO dbo.Contacts_Archive (ContactId, Email, DateCreation)
WHERE Rang > 1;
Vérifier l'équivalence#
Un changement de méthode ne vaut que si le résultat est identique. Comparer dans les deux sens ; aucune ligne ne doit sortir.
-- Plus aucun doublon, et exactement un contact par adresse
SELECT Email, COUNT(*) AS Nb FROM dbo.Contacts GROUP BY Email HAVING COUNT(*) > 1;
-- Résultat attendu : aucune ligne.
SELECT ContactId FROM dbo.Contacts ORDER BY ContactId;
-- Résultat attendu avec le jeu d'essai : 3 et 4.
Versions de SQL Server#
ROW_NUMBER et suppression à travers une CTE : SQL Server 2005 et ultérieur.