sql >> Databáze >  >> RDS >> Database

Uložená procedura k odstranění duplicitních záznamů v tabulce SQL

Někdy během našeho běhu jako DBA narazíme na alespoň jednu tabulku, která je načtena duplicitními záznamy. I když má tabulka primární klíč (ve většině případů automaticky přírůstkový), ostatní pole mohou mít duplicitní hodnoty.

SQL Server však umožňuje mnoho způsobů, jak se těchto duplicitních záznamů zbavit (např. pomocí CTE, funkce SQL Rank, poddotazů s Group By atd.).

Pamatuji si, jak jsem se jednou během rozhovoru zeptal, jak odstranit duplicitní záznamy v tabulce a ponechat pouze 1 z každého. Tehdy jsem nebyl schopen odpovědět, ale byl jsem velmi zvědavý. Po malém zkoumání jsem našel spoustu možností, jak tento problém vyřešit.

Nyní, po letech, jsem tu, abych vám představil uloženou proceduru, jejímž cílem je odpovědět na otázku „jak odstranit duplicitní záznamy v tabulce SQL? Každý DBA jej může jednoduše použít k nějakému úklidu, aniž by se příliš staral.

Vytvoření uložené procedury:Počáteční úvahy

Účet, který používáte, musí mít dostatečná oprávnění k vytvoření uložené procedury v zamýšlené databázi.

Účet spouštějící tuto uloženou proceduru musí mít dostatečná oprávnění k provádění operací SELECT a DELETE proti cílové databázové tabulce.

Tato uložená procedura je určena pro databázové tabulky, které nemají definovaný primární klíč (ani omezení UNIQUE). Pokud však vaše tabulka obsahuje primární klíč, uložená procedura tato pole nezohlední. Provede vyhledání a odstranění na základě zbývajících polí (takže je v tomto případě používejte velmi opatrně).

Jak používat uloženou proceduru v SQL

Zkopírujte a vložte kód SP T-SQL dostupný v tomto článku. SP očekává 3 parametry:

@schemaName – název schématu databázové tabulky, pokud se použije. Pokud ne, použijte dbo .

@tableName – název databázové tabulky, kde jsou uloženy duplicitní hodnoty.

@displayOnly – pokud je nastaveno na 1 , skutečné duplicitní záznamy nebudou smazány , ale zobrazí se pouze místo toho (pokud existuje). Ve výchozím nastavení je tato hodnota nastavena na 0 což znamená, že k skutečnému smazání dojde pokud existují duplikáty.

Uložená procedura SQL Server Testy provedení

Abych demonstroval uloženou proceduru, vytvořil jsem dvě různé tabulky – jednu bez primárního klíče a jednu s primárním klíčem. Do těchto tabulek jsem vložil nějaké fiktivní záznamy. Pojďme se podívat, jaké výsledky dostanu před/po provedení uložené procedury.

Tabulka SQL s primárním klíčem

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

Uložená procedura SQL Příklady záznamů

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

Provést uloženou proceduru pouze se zobrazením

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

Vzhledem k tomu, že sloupce1 a sloupec2 tvoří primární klíč, jsou duplikáty vyhodnoceny proti sloupcům jiného než primárního klíče, v tomto případě sloupce3. Výsledek je správný.

Provést uloženou proceduru pouze bez zobrazení

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

Duplicitní záznamy jsou pryč.

S tímto přístupem však musíte být opatrní, protože první výskyt záznamu je ten, který bude řezat. Pokud tedy z jakéhokoli důvodu potřebujete smazat velmi konkrétní záznam, musíte svůj konkrétní případ řešit samostatně.

SQL Tabulka bez primárního klíče

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

Uložená procedura SQL Příklady záznamů

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

Provést uloženou proceduru pouze se zobrazením

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

Výstup je správný, to jsou duplicitní záznamy v tabulce.

Provést uloženou proceduru pouze bez zobrazení

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

Uložená procedura fungovala podle očekávání a duplikáty byly úspěšně vyčištěny.

Zvláštní případy pro tuto uloženou proceduru v SQL

Pokud zadané schéma nebo tabulka ve vaší databázi neexistuje, uložená procedura vás upozorní a skript ukončí své provádění.

Pokud necháte název schématu prázdný, skript vás upozorní a ukončí jeho provádění.

Pokud necháte název tabulky prázdný, skript vás na to upozorní a ukončí jeho provádění.

Pokud provedete uloženou proceduru proti tabulce, která nemá žádné duplikáty, a aktivujete bit @displayOnly , dostanete prázdnou sadu výsledků.

Uložená procedura SQL Server:Dokončete kód

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

Závěr

Pokud nevíte, jak odstranit duplicitní záznamy v SQL tabulce, budou pro vás užitečné nástroje, jako je tento. Jakýkoli správce databází může zkontrolovat, zda existují databázové tabulky, které pro ně nemají primární klíče (ani jedinečná omezení), což by mohlo časem nashromáždit hromadu nepotřebných záznamů (potenciálně plýtvat úložištěm). Stačí zapojit a spustit uloženou proceduru a můžete začít.

Můžete jít ještě o něco dále a vytvořit výstražný mechanismus, který vás upozorní, pokud existují duplikáty pro konkrétní tabulku (samozřejmě po implementaci trochu automatizace pomocí tohoto nástroje), což je docela užitečné.

Stejně jako u všeho, co souvisí s úlohami DBA, ujistěte se, že vždy vše otestujete v prostředí izolovaného prostoru, než stisknete spoušť v produkci. A když tak učiníte, ujistěte se, že máte zálohu tabulky, na kterou se zaměřujete.


  1. Uživatelská oprávnění SQLite

  2. Jak přeložit funkci PostgreSQL array_agg do SQLite?

  3. Škálování databáze Moodle

  4. Jak se mohu připojit k externí databázi z příkazu SQL nebo uložené procedury?