sql >> Databasteknik >  >> RDS >> Database

Lagrad procedur för att ta bort dubbletter av poster i SQL-tabell

Ibland under vår körning som DBA:er stöter vi på minst en tabell som är laddad med dubbletter av poster. Även om tabellen har en primärnyckel (en automatiskt inkrementell sådan i de flesta fall), kan resten av fälten ha dubbletter av värden.

SQL Server tillåter dock många sätt att bli av med dessa dubbletter av poster (t.ex. genom att använda CTE, SQL Rank-funktion, underfrågor med Group By, etc.).

Jag minns en gång, under en intervju, jag blev tillfrågad om hur man raderar dubbletter i en tabell samtidigt som man bara lämnar en av varje. Vid den tiden kunde jag inte svara, men jag var väldigt nyfiken. Efter att ha undersökt lite, hittade jag många alternativ för att lösa det här problemet.

Nu, år senare, är jag här för att presentera en lagrad procedur som syftar till att svara på frågan "hur man tar bort dubbletter av poster i SQL-tabellen?". Vilken DBA som helst kan helt enkelt använda den för att göra lite hushållning utan att oroa dig för mycket.

Skapa lagrad procedur:Inledande överväganden

Kontot du använder måste ha tillräckligt med privilegier för att skapa en lagrad procedur i den avsedda databasen.

Kontot som kör denna lagrade procedur måste ha tillräckligt med privilegier för att utföra SELECT- och DELETE-operationerna mot måldatabastabellen.

Denna lagrade procedur är avsedd för databastabeller som inte har en primärnyckel (eller en UNIK begränsning) definierad. Men om ditt bord har en primärnyckel, kommer den lagrade proceduren inte att ta hänsyn till dessa fält. Det kommer att utföra sökningen och raderingen baserat på resten av fälten (så använd det mycket noggrant i det här fallet).

Hur man använder lagrad procedur i SQL

Kopiera och klistra in SP T-SQL-koden som finns i den här artikeln. SP:n förväntar sig 3 parametrar:

@schemaName – namnet på databastabellschemat om tillämpligt. Om inte – använd dbo .

@tabellnamn – namnet på databastabellen där dubblettvärdena lagras.

@displayOnly – om satt till 1 , de faktiska dubblettposterna kommer inte att raderas , men bara visas istället (om någon). Som standard är detta värde inställt på 0 vilket betyder att den faktiska raderingen kommer att ske om det finns dubbletter.

SQL-server lagrad procedur Exekveringstester

För att demonstrera den lagrade proceduren har jag skapat två olika tabeller – en utan en primärnyckel och en med en primärnyckel. Jag har infogat några dummyposter i dessa tabeller. Låt oss kontrollera vilka resultat jag får före/efter att ha kört den lagrade proceduren.

SQL-tabell med primärnyckel

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 lagrad procedur Exempelposter

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

Utför lagrad procedur med endast bildskärm

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

Eftersom kolumn1 och kolumn2 utgör den primära nyckeln, utvärderas dubbletterna mot kolumnerna som inte är primärnyckeln, i det här fallet kolumn3. Resultatet är korrekt.

Utför lagrad procedur utan endast visning

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

Dubblettposterna är borta.

Du måste dock vara försiktig med detta tillvägagångssätt eftersom den första förekomsten av posten är den som kommer att klippa. Så om du av någon anledning behöver en mycket specifik post för att raderas, måste du ta itu med ditt specifika fall separat.

SQL tabell utan primärnyckel

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

SQL lagrad procedur Exempelposter

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

Utför lagrad procedur med endast bildskärm

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

Utdata är korrekt, det är dubblettposterna i tabellen.

Utför lagrad procedur utan endast visning

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

Den lagrade proceduren har fungerat som förväntat och dubbletterna har rensats.

Specialfall för denna lagrade procedur i SQL

Om schemat eller tabellen du anger inte finns i din databas kommer den lagrade proceduren att meddela dig och skriptet avslutar sin körning.

Om du lämnar schemanamnet tomt kommer skriptet att meddela dig och avsluta sin körning.

Om du lämnar tabellnamnet tomt kommer skriptet att meddela dig och avsluta sin körning.

Om du kör den lagrade proceduren mot en tabell som inte har några dubbletter och aktiverar @displayOnly-biten , får du en tom resultatuppsättning.

SQL Server lagrad procedur:Komplett kod

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

Slutsats

Om du inte vet hur man tar bort dubbletter av poster i SQL-tabellen kommer verktyg som detta att vara till hjälp för dig. Vilken DBA som helst kan kontrollera om det finns databastabeller som inte har primärnycklar (eller unika begränsningar) för dem, som kan ackumulera en hög med onödiga poster över tiden (potentiellt slöseri med lagring). Bara koppla in och spela den lagrade proceduren så är du igång.

Du kan gå lite längre och bygga en varningsmekanism för att meddela dig om det finns dubbletter för en specifik tabell (efter att ha implementerat lite automatisering med det här verktyget förstås), vilket är ganska praktiskt.

Som med allt relaterat till DBA-uppgifter, se till att alltid testa allt i en sandlådemiljö innan du trycker på avtryckaren i produktionen. Och när du gör det, se till att ha en säkerhetskopia av tabellen du fokuserar på.


  1. Varför återvänder pg_restore framgångsrikt men återställer inte min databas?

  2. RowGen v3 automatiserar generering av databastestdata

  3. Algoritm för att undvika SQL-injektion på MSSQL Server från C#-kod?

  4. Gör fantastiska listor själv, eller GitHub som anteckningsbok