Parfois, lors de notre exécution en tant que DBA, nous rencontrons au moins une table chargée d'enregistrements en double. Même si la table a une clé primaire (auto-incrémentielle dans la plupart des cas), le reste des champs peut avoir des valeurs en double.
Cependant, SQL Server permet de nombreuses façons de se débarrasser de ces enregistrements en double (par exemple, en utilisant les CTE, la fonction SQL Rank, les sous-requêtes avec Group By, etc.).
Je me souviens qu'une fois, lors d'une interview, on m'a demandé comment supprimer les enregistrements en double dans une table tout en n'en laissant qu'un de chaque. À ce moment-là, je n'étais pas en mesure de répondre, mais j'étais très curieux. Après quelques recherches, j'ai trouvé de nombreuses options pour résoudre ce problème.
Maintenant, des années plus tard, je suis ici pour vous présenter une procédure stockée qui vise à répondre à la question "comment supprimer les enregistrements en double dans la table SQL ?". N'importe quel DBA peut simplement l'utiliser pour faire du ménage sans trop s'inquiéter.
Créer une procédure stockée :Considérations initiales
Le compte que vous utilisez doit avoir suffisamment de privilèges pour créer une procédure stockée dans la base de données prévue.
Le compte exécutant cette procédure stockée doit avoir suffisamment de privilèges pour effectuer les opérations SELECT et DELETE sur la table de base de données cible.
Cette procédure stockée est destinée aux tables de base de données qui n'ont pas de clé primaire (ni de contrainte UNIQUE) définie. Cependant, si votre table a une clé primaire, la procédure stockée ne prendra pas ces champs en compte. Il effectuera la recherche et la suppression en fonction du reste des champs (utilisez-le donc très attentivement dans ce cas).
Comment utiliser la procédure stockée en SQL
Copiez et collez le code SP T-SQL disponible dans cet article. Le SP attend 3 paramètres :
@schemaName – le nom du schéma de la table de la base de données, le cas échéant. Sinon, utilisez dbo .
@tableName – le nom de la table de la base de données où sont stockées les valeurs en double.
@displayOnly – si défini sur 1 , les enregistrements en double réels ne seront pas supprimés , mais seulement affiché à la place (le cas échéant). Par défaut, cette valeur est définie sur 0 ce qui signifie que la suppression réelle aura lieu si des doublons existent.
Tests d'exécution de procédure stockée SQL Server
Pour illustrer la procédure stockée, j'ai créé deux tables différentes - une sans clé primaire et une avec une clé primaire. J'ai inséré des enregistrements factices dans ces tables. Vérifions les résultats que j'obtiens avant/après l'exécution de la procédure stockée.
Table SQL avec clé primaire
CREATE TABLE [dbo].[test](
[column1] [varchar](16) NOT NULL,
[column2] [varchar](16) NOT NULL,
[column3] [varchar](16) NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[column1] ASC,
[column2] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Procédure stockée SQL Exemple d'enregistrements
INSERT INTO test VALUES('A','A',1),('A','B',1),('A','C',1),('B','A',2),('B','B',3),('B','C',4)
Exécuter la procédure stockée avec affichage uniquement
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 1
Étant donné que la colonne1 et la colonne2 forment la clé primaire, les doublons sont évalués par rapport aux colonnes non clés primaires, dans ce cas, la colonne3. Le résultat est correct.
Exécuter la procédure stockée sans affichage uniquement
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 0
Les enregistrements en double ont disparu.
Cependant, vous devez être prudent avec cette approche car la première occurrence de l'enregistrement est celle qui sera coupée. Donc, si pour une raison quelconque vous avez besoin qu'un enregistrement très spécifique soit supprimé, vous devez traiter votre cas particulier séparément.
Table SQL sans clé primaire
CREATE TABLE [dbo].[duplicates](
[column1] [varchar](16) NOT NULL,
[column2] [varchar](16) NOT NULL,
[column3] [varchar](16) NOT NULL
) ON [PRIMARY]
GO
Procédure stockée SQL Exemple d'enregistrements
INSERT INTO duplicates VALUES
('John','Smith','Y'),
('John','Smith','Y'),
('John','Smith','N'),
('Peter','Parker','N'),
('Bruce','Wayne','Y'),
('Steve','Rogers','Y'),
('Steve','Rogers','Y'),
('Tony','Stark','N')
Exécuter la procédure stockée avec affichage uniquement
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 1
La sortie est correcte, ce sont les enregistrements en double dans la table.
Exécuter la procédure stockée sans affichage uniquement
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 0
La procédure stockée a fonctionné comme prévu et les doublons ont été nettoyés avec succès.
Cas particuliers pour cette procédure stockée en SQL
Si le schéma ou la table que vous spécifiez n'existe pas dans votre base de données, la procédure stockée vous en informera et le script terminera son exécution.
Si vous laissez le nom du schéma vide, le script vous avertira et terminera son exécution.
Si vous laissez le nom de la table vide, le script vous avertira et terminera son exécution.
Si vous exécutez la procédure stockée sur une table qui n'a pas de doublons et activez le bit @displayOnly , vous obtiendrez un jeu de résultats vide.
Procédure stockée SQL Server :code complet
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author : Alejandro Cobar
-- Create date: 2021-06-01
-- Description: SP to delete duplicate rows in a table
-- =============================================
CREATE PROCEDURE DBA_DeleteDuplicates
@schemaName VARCHAR(128),
@tableName VARCHAR(128),
@displayOnly BIT = 0
AS
BEGIN
SET NOCOUNT ON;
IF LEN(@schemaName) = 0
BEGIN
PRINT 'You must specify the schema of the table!'
RETURN
END
IF LEN(@tableName) = 0
BEGIN
PRINT 'You must specify the name of the table!'
RETURN
END
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schemaName AND TABLE_NAME = @tableName)
BEGIN
DECLARE @pkColumnName VARCHAR(128);
DECLARE @columnName VARCHAR(128);
DECLARE @sqlCommand VARCHAR(MAX);
DECLARE @columnsList VARCHAR(MAX);
DECLARE @pkColumnsList VARCHAR(MAX);
DECLARE @pkColumns TABLE(pkColumn VARCHAR(128));
DECLARE @limit INT;
INSERT INTO @pkColumns
SELECT K.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K ON C.TABLE_NAME = K.TABLE_NAME AND C.CONSTRAINT_SCHEMA = K.CONSTRAINT_SCHEMA
WHERE C.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND C.CONSTRAINT_SCHEMA = @schemaName AND C.TABLE_NAME = @tableName
IF((SELECT COUNT(*) FROM @pkColumns) > 0)
BEGIN
DECLARE pk_cursor CURSOR FOR
SELECT * FROM @pkColumns
OPEN pk_cursor
FETCH NEXT FROM pk_cursor INTO @pkColumnName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @pkColumnsList = CONCAT(@pkColumnsList,'',@pkColumnName,',')
FETCH NEXT FROM pk_cursor INTO @pkColumnName
END
CLOSE pk_cursor
DEALLOCATE pk_cursor
SET @pkColumnsList = SUBSTRING(@pkColumnsList,1,LEN(@pkColumnsList)-1)
END
DECLARE columns_cursor CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @schemaName AND TABLE_NAME = @tableName AND COLUMN_NAME NOT IN (SELECT pkColumn FROM @pkColumns)
ORDER BY ORDINAL_POSITION;
OPEN columns_cursor
FETCH NEXT FROM columns_cursor INTO @columnName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @columnsList = CONCAT(@columnsList,'',@columnName,',')
FETCH NEXT FROM columns_cursor INTO @columnName
END
CLOSE columns_cursor
DEALLOCATE columns_cursor
SET @columnsList = SUBSTRING(@columnsList,1,LEN(@columnsList)-1)
IF((SELECT COUNT(*) FROM @pkColumns) > 0)
BEGIN
IF(CHARINDEX(',',@columnsList) = 0)
SET @limit = LEN(@columnsList)+1
ELSE
SET @limit = CHARINDEX(',',@columnsList)
SET @sqlCommand = CONCAT('WITH CTE (',@columnsList,',DuplicateCount',')
AS (SELECT ',@columnsList,',',
'ROW_NUMBER() OVER(PARTITION BY ',@columnsList,' ',
'ORDER BY ',SUBSTRING(@columnsList,1,@limit-1),') AS DuplicateCount
FROM [',@schemaName,'].[',@tableName,'])
')
IF @displayOnly = 0
SET @sqlCommand = CONCAT(@sqlCommand,'DELETE FROM CTE WHERE DuplicateCount > 1;')
IF @displayOnly = 1
SET @sqlCommand = CONCAT(@sqlCommand,'SELECT ',@columnsList,',MAX(DuplicateCount) AS DuplicateCount FROM CTE WHERE DuplicateCount > 1 GROUP BY ',@columnsList)
END
ELSE
BEGIN
SET @sqlCommand = CONCAT('WITH CTE (',@columnsList,',DuplicateCount',')
AS (SELECT ',@columnsList,',',
'ROW_NUMBER() OVER(PARTITION BY ',@columnsList,' ',
'ORDER BY ',SUBSTRING(@columnsList,1,CHARINDEX(',',@columnsList)-1),') AS DuplicateCount
FROM [',@schemaName,'].[',@tableName,'])
')
IF @displayOnly = 0
SET @sqlCommand = CONCAT(@sqlCommand,'DELETE FROM CTE WHERE DuplicateCount > 1;')
IF @displayOnly = 1
SET @sqlCommand = CONCAT(@sqlCommand,'SELECT * FROM CTE WHERE DuplicateCount > 1;')
END
EXEC (@sqlCommand)
END
ELSE
BEGIN
PRINT 'Table doesn't exist within this database!'
RETURN
END
END
GO
Conclusion
Si vous ne savez pas comment supprimer les enregistrements en double dans la table SQL, des outils comme celui-ci vous seront utiles. N'importe quel DBA peut vérifier s'il existe des tables de base de données qui n'ont pas de clés primaires (ni de contraintes uniques) pour elles, qui pourraient accumuler une pile d'enregistrements inutiles au fil du temps (ce qui pourrait gaspiller de l'espace de stockage). Il suffit de brancher et de jouer la procédure stockée, et vous êtes prêt à partir.
Vous pouvez aller un peu plus loin et créer un mécanisme d'alerte pour vous avertir s'il y a des doublons pour une table spécifique (après avoir implémenté un peu d'automatisation à l'aide de cet outil bien sûr), ce qui est très pratique.
Comme pour tout ce qui concerne les tâches DBA, assurez-vous de toujours tout tester dans un environnement sandbox avant d'appuyer sur la gâchette en production. Et lorsque vous le faites, assurez-vous d'avoir une sauvegarde de la table sur laquelle vous vous concentrez.