Database
 sql >> Base de données >  >> RDS >> Database

Procédure stockée pour supprimer les enregistrements en double dans la table SQL

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.