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

Lagret procedure til sletning af duplikerede poster i SQL-tabel

Nogle gange under vores kørsel som DBA'er støder vi på mindst én tabel, der er fyldt med duplikerede poster. Selvom tabellen har en primær nøgle (en auto-inkrementel i de fleste tilfælde), kan resten af ​​felterne have duplikerede værdier.

SQL Server giver dog mulighed for mange måder at slippe af med disse duplikerede poster (f.eks. ved hjælp af CTE'er, SQL Rank-funktion, underforespørgsler med Group By osv.).

Jeg kan huske, at jeg engang under et interview blev spurgt om, hvordan man sletter duplikerede poster i en tabel, mens jeg kun efterlader 1 af hver. På det tidspunkt var jeg ikke i stand til at svare, men jeg var meget nysgerrig. Efter at have undersøgt lidt, fandt jeg masser af muligheder for at løse dette problem.

Nu, år senere, er jeg her for at præsentere dig for en lagret procedure, der har til formål at besvare spørgsmålet "hvordan sletter du dublerede poster i SQL-tabel?". Enhver DBA kan simpelthen bruge det til at lave noget husholdning uden at bekymre sig for meget.

Opret lagret procedure:Indledende overvejelser

Den konto, du bruger, skal have nok privilegier til at oprette en lagret procedure i den tilsigtede database.

Kontoen, der udfører denne lagrede procedure, skal have tilstrækkelige privilegier til at udføre SELECT- og DELETE-handlingerne mod måldatabasetabellen.

Denne lagrede procedure er beregnet til databasetabeller, der ikke har en primær nøgle (eller en UNIK begrænsning) defineret. Men hvis din tabel har en primær nøgle, vil den lagrede procedure ikke tage højde for disse felter. Det vil udføre opslag og sletning baseret på resten af ​​felterne (så brug det meget forsigtigt i dette tilfælde).

Sådan bruges lagret procedure i SQL

Kopiér og indsæt SP T-SQL-koden, der er tilgængelig i denne artikel. SP forventer 3 parametre:

@schemaName – navnet på databasetabelskemaet, hvis det er relevant. Hvis ikke – brug dbo .

@tableName – navnet på databasetabellen, hvor duplikatværdierne er gemt.

@displayOnly – hvis indstillet til 1 , vil de faktiske dubletposter ikke blive slettet , men kun vist i stedet (hvis nogen). Som standard er denne værdi sat til 0 hvilket betyder, at den faktiske sletning vil ske hvis der findes dubletter.

SQL Server Stored Procedure Udførelsestest

For at demonstrere den lagrede procedure har jeg lavet to forskellige tabeller – en uden en primær nøgle og en med en primær nøgle. Jeg har indsat nogle dummy-poster i disse tabeller. Lad os tjekke, hvilke resultater jeg får før/efter udførelse af den lagrede procedure.

SQL-tabel med primærnøgle

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 Stored Procedure Eksempelposter

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

Udfør lagret procedure med kun skærm

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

Da kolonne1 og kolonne2 udgør den primære nøgle, evalueres duplikaterne mod de ikke-primære nøglekolonner, i dette tilfælde kolonne3. Resultatet er korrekt.

Udfør lagret procedure uden kun visning

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

Duplikatposterne er væk.

Du skal dog være forsigtig med denne tilgang, fordi den første forekomst af posten er den, der vil skære. Så hvis du af en eller anden grund har brug for en meget specifik post for at blive slettet, så skal du behandle din særlige sag separat.

SQL Tabel uden primærnøgle

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

SQL Stored Procedure Eksempelposter

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

Udfør lagret procedure med kun skærm

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

Outputtet er korrekt, det er de duplikerede poster i tabellen.

Udfør lagret procedure uden kun visning

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

Den lagrede procedure har fungeret som forventet, og dubletterne er blevet renset.

Særlige tilfælde for denne lagrede procedure i SQL

Hvis skemaet eller tabellen, du angiver, ikke findes i din database, vil den lagrede procedure give dig besked, og scriptet afslutter sin udførelse.

Hvis du lader skemanavnet stå tomt, vil scriptet give dig besked og afslutte dets eksekvering.

Hvis du lader tabelnavnet stå tomt, vil scriptet give dig besked og afslutte dets udførelse.

Hvis du udfører den lagrede procedure mod en tabel, der ikke har nogen dubletter og aktiverer @displayOnly bit , får du et tomt resultatsæt.

SQL Server Stored Procedure:Fuldfør kode

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

Konklusion

Hvis du ikke ved, hvordan du sletter duplikerede poster i SQL-tabel, vil værktøjer som dette være nyttige for dig. Enhver DBA kan kontrollere, om der er databasetabeller, der ikke har primære nøgler (heller ikke unikke begrænsninger) for dem, som kan akkumulere en bunke unødvendige poster over tid (potentielt spild af lagerplads). Du skal bare tilslutte og afspille den lagrede procedure, så er du i gang.

Du kan gå lidt længere og bygge en advarselsmekanisme til at give dig besked, hvis der er dubletter til en specifik tabel (efter at have implementeret en smule automatisering ved hjælp af dette værktøj selvfølgelig), hvilket er ret praktisk.

Som med alt relateret til DBA-opgaver, skal du sørge for altid at teste alt i et sandkassemiljø, før du trykker på aftrækkeren i produktionen. Og når du gør det, så sørg for at have en sikkerhedskopi af bordet, du fokuserer på.


  1. Sådan sammenlignes datoer i SQL Server

  2. Sådan forespørges JSON-kolonnen i MySQL

  3. PostgreSQL-procesnavne på Solaris

  4. Hvordan ændrer jeg db-skema til dbo