sql >> Database >  >> RDS >> Database

Opgeslagen procedure om dubbele records in SQL-tabel te verwijderen

Soms komen we tijdens onze run als DBA's ten minste één tabel tegen die is geladen met dubbele records. Zelfs als de tabel een primaire sleutel heeft (in de meeste gevallen een automatisch oplopende sleutel), kunnen de rest van de velden dubbele waarden hebben.

SQL Server biedt echter vele manieren om van die dubbele records af te komen (bijv. met behulp van CTE's, SQL Rank-functie, subquery's met Group By, enz.).

Ik herinner me een keer, tijdens een interview, dat mij werd gevraagd hoe dubbele records in een tabel te verwijderen terwijl er slechts 1 van elk overbleef. Op dat moment kon ik niet antwoorden, maar ik was erg nieuwsgierig. Na wat onderzoek heb ik genoeg opties gevonden om dit probleem op te lossen.

Nu, jaren later, ben ik hier om u een opgeslagen procedure te presenteren die tot doel heeft de vraag te beantwoorden "hoe dubbele records in de SQL-tabel te verwijderen?". Elke DBA kan het gewoon gebruiken om wat huishoudelijk werk te doen zonder zich al te veel zorgen te maken.

Opgeslagen procedure maken:eerste overwegingen

Het account dat u gebruikt, moet voldoende rechten hebben om een ​​Opgeslagen Procedure aan te maken in de beoogde database.

Het account dat deze opgeslagen procedure uitvoert, moet voldoende bevoegdheden hebben om de bewerkingen SELECT en DELETE uit te voeren op de doeldatabasetabel.

Deze opgeslagen procedure is bedoeld voor databasetabellen waarvoor geen primaire sleutel (noch een UNIEKE beperking) is gedefinieerd. Als uw tabel echter een primaire sleutel heeft, houdt de opgeslagen procedure geen rekening met die velden. Het zal het opzoeken en verwijderen uitvoeren op basis van de rest van de velden (gebruik het dus heel voorzichtig in dit geval).

Opgeslagen procedure gebruiken in SQL

Kopieer en plak de SP T-SQL-code die beschikbaar is in dit artikel. De SP verwacht 3 parameters:

@schemaName – de naam van het databasetabelschema, indien van toepassing. Zo niet – gebruik dbo .

@tableName – de naam van de databasetabel waarin de dubbele waarden zijn opgeslagen.

@displayOnly – indien ingesteld op 1 , de daadwerkelijke dubbele records worden niet verwijderd , maar alleen in plaats daarvan weergegeven (indien aanwezig). Standaard is deze waarde ingesteld op 0 wat betekent dat de daadwerkelijke verwijdering zal plaatsvinden als er duplicaten zijn.

SQL Server Opgeslagen Procedure Uitvoeringstests

Om de opgeslagen procedure te demonstreren, heb ik twee verschillende tabellen gemaakt - een zonder een primaire sleutel en een met een primaire sleutel. Ik heb een aantal dummy records in deze tabellen opgenomen. Laten we eens kijken welke resultaten ik krijg voor/na het uitvoeren van de Opgeslagen Procedure.

SQL-tabel met primaire sleutel

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

SQL-opgeslagen procedure Voorbeeldrecords

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

Opgeslagen procedure uitvoeren met alleen weergave

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

Aangezien kolom1 en kolom2 de primaire sleutel vormen, worden de duplicaten vergeleken met de niet-primaire sleutelkolommen, in dit geval kolom3. Het resultaat is correct.

Opgeslagen procedure uitvoeren zonder alleen weergave

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

De dubbele records zijn verdwenen.

U moet echter voorzichtig zijn met deze benadering, omdat de eerste keer dat het record voorkomt, het record wordt verbroken. Dus als u om welke reden dan ook een heel specifiek record wilt verwijderen, moet u uw specifieke geval afzonderlijk behandelen.

SQL Tabel zonder primaire sleutel

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

SQL-opgeslagen procedure Voorbeeldrecords

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

Opgeslagen procedure uitvoeren met alleen weergave

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

De output is correct, dat zijn de dubbele records in de tabel.

Opgeslagen procedure uitvoeren zonder alleen weergave

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

De Opgeslagen Procedure heeft gewerkt zoals verwacht en de duplicaten zijn met succes opgeschoond.

Speciale gevallen voor deze opgeslagen procedure in SQL

Als het schema of de tabel die u opgeeft niet in uw database bestaat, zal de Opgeslagen Procedure u hiervan op de hoogte stellen en zal het script de uitvoering beëindigen.

Als u de naam van het schema leeg laat, zal het script u hiervan op de hoogte stellen en de uitvoering beëindigen.

Als u de tabelnaam leeg laat, zal het script u hiervan op de hoogte stellen en de uitvoering beëindigen.

Als u de Opgeslagen procedure uitvoert op een tabel die geen duplicaten heeft en de @displayOnly-bit activeert , krijgt u een lege resultatenset.

Opgeslagen SQL Server-procedure:volledige code

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

Conclusie

Als u niet weet hoe u dubbele records in de SQL-tabel moet verwijderen, dan zullen dergelijke tools nuttig voor u zijn. Elke DBA kan controleren of er databasetabellen zijn die geen primaire sleutels (noch unieke beperkingen) hebben, die in de loop van de tijd een stapel onnodige records kunnen ophopen (mogelijk opslagruimte verspillend). Sluit gewoon de Opgeslagen procedure aan en speel af, en u kunt aan de slag.

Je kunt een beetje verder gaan en een waarschuwingsmechanisme bouwen om je op de hoogte te stellen als er duplicaten zijn voor een specifieke tabel (na natuurlijk een beetje automatisering te hebben geïmplementeerd met behulp van deze tool), wat best handig is.

Zoals met alles met betrekking tot DBA-taken, moet u ervoor zorgen dat u altijd alles in een sandbox-omgeving test voordat u de trekker overhaalt in productie. En als je dat doet, zorg er dan voor dat je een back-up hebt van de tafel waarop je focust.


  1. Hoe te verwijzen naar JSON-sleutels die speciale tekens bevatten bij gebruik van OPENJSON, JSON_QUERY en JSON_VALUE (SQL Server)

  2. MySQL> Tabel bestaat niet. Maar het doet (of zou moeten)

  3. Waarom zou je PostgreSQL leren?

  4. NULL-waarden binnen de NOT IN-clausule