sql >> Base de Datos >  >> RDS >> Database

Procedimiento almacenado para eliminar registros duplicados en la tabla SQL

A veces, durante nuestra ejecución como DBA, nos encontramos con al menos una tabla que está cargada con registros duplicados. Incluso si la tabla tiene una clave principal (autoincremental en la mayoría de los casos), el resto de los campos pueden tener valores duplicados.

Sin embargo, SQL Server permite muchas maneras de deshacerse de esos registros duplicados (por ejemplo, usando CTE, la función de clasificación de SQL, subconsultas con Agrupar por, etc.).

Recuerdo una vez, durante una entrevista, me preguntaron cómo eliminar registros duplicados en una tabla dejando solo 1 de cada uno. En ese momento, no pude responder, pero tenía mucha curiosidad. Después de investigar un poco, encontré muchas opciones para resolver este problema.

Ahora, años después, estoy aquí para presentarles un procedimiento almacenado que tiene como objetivo responder a la pregunta "¿cómo eliminar registros duplicados en la tabla SQL?". Cualquier DBA puede usarlo simplemente para hacer algunas tareas domésticas sin preocuparse demasiado.

Crear procedimiento almacenado:consideraciones iniciales

La cuenta que utilice debe tener suficientes privilegios para crear un procedimiento almacenado en la base de datos prevista.

La cuenta que ejecuta este procedimiento almacenado debe tener suficientes privilegios para realizar las operaciones SELECCIONAR y ELIMINAR en la tabla de la base de datos de destino.

Este procedimiento almacenado está destinado a las tablas de la base de datos que no tienen una clave principal (ni una restricción ÚNICA) definida. Sin embargo, si su tabla tiene una clave principal, el procedimiento almacenado no tendrá en cuenta esos campos. Realizará la búsqueda y la eliminación en función del resto de los campos (así que utilícelo con mucho cuidado en este caso).

Cómo usar el procedimiento almacenado en SQL

Copie y pegue el código SP T-SQL disponible en este artículo. El SP espera 3 parámetros:

@schemaName – el nombre del esquema de la tabla de la base de datos, si corresponde. Si no, use dbo .

@nombreDeLaTabla – el nombre de la tabla de la base de datos donde se almacenan los valores duplicados.

@displayOnly – si se establece en 1 , los registros duplicados reales no se eliminarán , pero solo se muestra en su lugar (si corresponde). De forma predeterminada, este valor se establece en 0 lo que significa que la eliminación real ocurrirá si existen duplicados.

Procedimiento almacenado de SQL Server Pruebas de ejecución

Para demostrar el procedimiento almacenado, he creado dos tablas diferentes:una sin clave principal y otra con clave principal. He insertado algunos registros ficticios en estas tablas. Veamos qué resultados obtengo antes/después de ejecutar el procedimiento almacenado.

Tabla SQL con clave principal

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

Procedimiento almacenado de SQL Registros de ejemplo

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

Ejecutar procedimiento almacenado solo con visualización

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

Dado que la columna 1 y la columna 2 forman la clave principal, los duplicados se evalúan con las columnas que no son la clave principal, en este caso, la columna 3. El resultado es correcto.

Ejecutar procedimiento almacenado sin visualización solamente

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

Los registros duplicados se han ido.

Sin embargo, debe tener cuidado con este enfoque porque la primera aparición del registro es la que se cortará. Por lo tanto, si por alguna razón necesita que se elimine un registro muy específico, entonces debe abordar su caso particular por separado.

SQL Tabla sin clave principal

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

Procedimiento almacenado de SQL Registros de ejemplo

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

Ejecutar procedimiento almacenado solo con visualización

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

El resultado es correcto, esos son los registros duplicados en la tabla.

Ejecutar procedimiento almacenado sin visualización solamente

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

El procedimiento almacenado funcionó como se esperaba y los duplicados se limpiaron con éxito.

Casos especiales para este procedimiento almacenado en SQL

Si el esquema o la tabla que está especificando no existe dentro de su base de datos, el procedimiento almacenado le notificará y el script finalizará su ejecución.

Si deja el nombre del esquema en blanco, el script le notificará y finalizará su ejecución.

Si deja el nombre de la tabla en blanco, el script le notificará y finalizará su ejecución.

Si ejecuta el procedimiento almacenado en una tabla que no tiene duplicados y activa el bit @displayOnly , obtendrá un conjunto de resultados vacío.

Procedimiento almacenado de SQL Server:código 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

Conclusión

Si no sabe cómo eliminar registros duplicados en una tabla SQL, herramientas como esta le serán útiles. Cualquier DBA puede verificar si hay tablas de base de datos que no tienen claves principales (ni restricciones únicas) para ellas, que podrían acumular una pila de registros innecesarios con el tiempo (potencialmente desperdiciando almacenamiento). Simplemente conecte y reproduzca el procedimiento almacenado y estará listo para comenzar.

Puede ir un poco más allá y crear un mecanismo de alerta que le notifique si hay duplicados para una tabla específica (después de implementar un poco de automatización con esta herramienta, por supuesto), lo que resulta bastante útil.

Al igual que con cualquier cosa relacionada con las tareas de DBA, asegúrese de probar siempre todo en un entorno de espacio aislado antes de apretar el gatillo en producción. Y cuando lo haga, asegúrese de tener una copia de seguridad de la tabla en la que se enfoca.