Database
 sql >> Database >  >> RDS >> Database

Stored procedure per eliminare i record duplicati nella tabella SQL

A volte durante la nostra esecuzione come DBA, ci imbattiamo in almeno una tabella caricata con record duplicati. Anche se la tabella ha una chiave primaria (nella maggior parte dei casi una chiave incrementale), il resto dei campi potrebbe avere valori duplicati.

Tuttavia, SQL Server consente molti modi per eliminare quei record duplicati (ad es. utilizzando CTE, funzione SQL Rank, sottoquery con Raggruppa per e così via).

Ricordo che una volta, durante un'intervista, mi è stato chiesto come eliminare i record duplicati in una tabella lasciando solo 1 di ciascuno. In quel momento non sono stato in grado di rispondere, ma ero molto curioso. Dopo aver cercato un po', ho trovato molte opzioni per risolvere questo problema.

Ora, anni dopo, sono qui per presentarvi una stored procedure che mira a rispondere alla domanda "come eliminare i record duplicati nella tabella SQL?". Qualsiasi DBA può semplicemente usarlo per fare alcune pulizie senza preoccuparsi troppo.

Crea stored procedure:considerazioni iniziali

L'account che utilizzi deve disporre di privilegi sufficienti per creare una stored procedure nel database previsto.

L'account che esegue questa stored procedure deve disporre di privilegi sufficienti per eseguire le operazioni SELECT ed DELETE sulla tabella del database di destinazione.

Questa stored procedure è destinata alle tabelle del database che non hanno una chiave primaria (né un vincolo UNIQUE) definita. Tuttavia, se la tabella dispone di una chiave primaria, la stored procedure non terrà conto di tali campi. Eseguirà la ricerca e l'eliminazione in base al resto dei campi (quindi usalo con molta attenzione in questo caso).

Come utilizzare la procedura archiviata in SQL

Copia e incolla il codice SP T-SQL disponibile in questo articolo. L'SP prevede 3 parametri:

@NomeSchema – il nome dello schema della tabella del database, se applicabile. In caso contrario, utilizzare dbo .

@NomeTabella – il nome della tabella del database in cui sono archiviati i valori duplicati.

@displayOnly – se impostato su 1 , i record duplicati effettivi non verranno eliminati , ma solo visualizzato (se presente). Per impostazione predefinita, questo valore è impostato su 0 il che significa che l'eliminazione effettiva avverrà se esistono duplicati.

Stored procedure di SQL Server test di esecuzione

Per dimostrare la stored procedure, ho creato due tabelle diverse:una senza chiave primaria e l'altra con chiave primaria. Ho inserito alcuni record fittizi in queste tabelle. Verifichiamo quali risultati ottengo prima/dopo l'esecuzione della stored procedure.

Tabella SQL con chiave primaria

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

Procedura archiviata SQL Record di esempio

INSERT INTO test VALUES('A','A',1),('A','B',1),('A','C',1),('B','A',2),('B','B',3),('B','C',4)

Esegui stored procedure con solo visualizzazione

EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 1

Poiché colonna1 e colonna2 costituiscono la chiave primaria, i duplicati vengono valutati rispetto alle colonne chiave non primaria, in questo caso colonna3. Il risultato è corretto.

Esegui stored procedure senza solo visualizzazione

EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 0

I record duplicati sono spariti.

Tuttavia, devi stare attento con questo approccio perché la prima occorrenza del record è quella che verrà tagliata. Quindi, se per qualsiasi motivo hai bisogno di eliminare un record molto specifico, devi affrontare il tuo caso particolare separatamente.

Tabella SQL senza chiave primaria

CREATE TABLE [dbo].[duplicates](
	[column1] [varchar](16) NOT NULL,
	[column2] [varchar](16) NOT NULL,
	[column3] [varchar](16) NOT NULL
) ON [PRIMARY]
GO

Procedura archiviata SQL Record di esempio

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')

Esegui stored procedure con solo visualizzazione

EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 1

L'output è corretto, quelli sono i record duplicati nella tabella.

Esegui stored procedure senza solo visualizzazione

EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 0

La stored procedure ha funzionato come previsto e i duplicati sono stati eliminati correttamente.

Casi speciali per questa stored procedure in SQL

Se lo schema o la tabella che stai specificando non esiste nel tuo database, la Stored Procedure ti avviserà e lo script terminerà la sua esecuzione.

Se lasci vuoto il nome dello schema, lo script ti avviserà e ne terminerà l'esecuzione.

Se lasci vuoto il nome della tabella, lo script ti avviserà e terminerà la sua esecuzione.

Se esegui la stored procedure su una tabella che non ha duplicati e attivi il bit @displayOnly , otterrai un set di risultati vuoto.

Procedura archiviata di SQL Server:codice completo

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

Conclusione

Se non sai come eliminare i record duplicati nella tabella SQL, strumenti come questo ti saranno utili. Qualsiasi DBA può verificare se ci sono tabelle di database che non hanno chiavi primarie (né vincoli univoci) per loro, che potrebbero accumulare una pila di record non necessari nel tempo (potenzialmente sprecando spazio di archiviazione). Collega e riproduci la stored procedure e sei a posto.

Puoi andare un po' oltre e creare un meccanismo di avviso per avvisarti se ci sono duplicati per una tabella specifica (dopo aver implementato un po' di automazione usando questo strumento, ovviamente), il che è abbastanza utile.

Come per qualsiasi cosa relativa alle attività DBA, assicurati di testare sempre tutto in un ambiente sandbox prima di premere il grilletto in produzione. E quando lo fai, assicurati di avere un backup della tabella su cui ti concentri.