Понякога по време на нашето изпълнение като DBA се натъкваме на поне една таблица, която е заредена с дублиращи се записи. Дори ако таблицата има първичен ключ (в повечето случаи с автоматично нарастване), останалите полета може да имат дублиращи се стойности.
Въпреки това, SQL Server позволява много начини да се отървете от тези дублиращи се записи (напр. използване на CTE, функция SQL Rank, подзаявки с Group By и т.н.).
Спомням си веднъж, по време на интервю, ме попитаха как да изтрия дублиращи се записи в таблица, като оставям само 1 от всеки. По това време не можех да отговоря, но бях много любопитна. След като проучих малко, намерих много възможности за разрешаване на този проблем.
Сега, години по-късно, аз съм тук, за да ви представя съхранена процедура, която има за цел да отговори на въпроса „как да изтрия дублиращи се записи в SQL таблица?“. Всеки DBA може просто да го използва, за да върши малко домакинство, без да се тревожи твърде много.
Създаване на съхранена процедура:Първоначални съображения
Акаунтът, който използвате, трябва да има достатъчно привилегии, за да създаде съхранена процедура в предвидената база данни.
Акаунтът, изпълняващ тази съхранена процедура, трябва да има достатъчно привилегии, за да изпълнява операциите SELECT и DELETE спрямо таблицата на целевата база данни.
Тази съхранена процедура е предназначена за таблиците на базата данни, които нямат дефиниран първичен ключ (нито ограничение UNIQUE). Въпреки това, ако вашата таблица има първичен ключ, съхранената процедура няма да вземе тези полета под внимание. Той ще извърши търсенето и изтриването въз основа на останалите полета (така че го използвайте много внимателно в този случай).
Как да използвате съхранена процедура в SQL
Копирайте и поставете SP T-SQL кода, наличен в тази статия. SP очаква 3 параметъра:
@schemaName – името на схемата на таблицата на базата данни, ако е приложимо. Ако не – използвайте dbo .
@tableName – името на таблицата на базата данни, където се съхраняват дублиращите се стойности.
@displayOnly – ако е зададено на 1 , действителните дублиращи се записи няма да бъдат изтрити , но се показва само вместо това (ако има такива). По подразбиране тази стойност е зададена на 0 което означава, че действителното изтриване ще се случи ако съществуват дубликати.
SQL Server Съхранена процедура Тестове за изпълнение
За да демонстрирам съхранената процедура, създадох две различни таблици – една без първичен ключ и една с първичен ключ. Вмъкнах някои фиктивни записи в тези таблици. Нека проверим какви резултати получавам преди/след изпълнение на съхранената процедура.
SQL таблица с първичен ключ
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 Съхранена процедура Примерни записи
INSERT INTO test VALUES('A','A',1),('A','B',1),('A','C',1),('B','A',2),('B','B',3),('B','C',4)
Изпълнете съхранена процедура само с дисплей
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 1
Тъй като колона 1 и колона 2 формират първичния ключ, дубликатите се оценяват спрямо колоните, които не са първичен ключ, в този случай колона 3. Резултатът е правилен.
Изпълнете съхранена процедура без само показване
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 0
Дублираните записи са изчезнали.
Въпреки това, трябва да внимавате с този подход, защото първото появяване на записа е това, което ще изреже. Така че, ако по някаква причина имате нужда от много конкретен запис да бъде изтрит, тогава трябва да се заемете с конкретния си случай отделно.
SQL Таблица без първичен ключ
CREATE TABLE [dbo].[duplicates](
[column1] [varchar](16) NOT NULL,
[column2] [varchar](16) NOT NULL,
[column3] [varchar](16) NOT NULL
) ON [PRIMARY]
GO
SQL Съхранена процедура Примерни записи
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')
Изпълнете съхранена процедура само с дисплей
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 1
Резултатът е правилен, това са дублиращите се записи в таблицата.
Изпълнете съхранена процедура без само показване
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 0
Съхранената процедура работи според очакванията и дубликатите са изчистени успешно.
Специални случаи за тази Съхранена процедура в SQL
Ако схемата или таблицата, които посочвате, не съществуват във вашата база данни, Съхранената процедура ще ви уведоми и скриптът ще прекрати изпълнението си.
Ако оставите името на схемата празно, скриптът ще ви уведоми и ще прекрати изпълнението му.
Ако оставите името на таблицата празно, скриптът ще ви уведоми и ще прекрати изпълнението му.
Ако изпълните съхранената процедура срещу таблица, която няма дубликати и активирате бита @displayOnly , ще получите празен набор от резултати.
Съхранена процедура на SQL Server:Пълен код
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
Заключение
Ако не знаете как да изтриете дублиращи се записи в SQL таблицата, инструменти като този ще ви бъдат полезни. Всеки DBA може да провери дали има таблици на база данни, които нямат първични ключове (нито уникални ограничения) за тях, които могат да натрупат купчина ненужни записи с течение на времето (потенциално загуба на съхранение). Просто включете и пуснете съхранената процедура и сте готови.
Можете да отидете малко по-далеч и да създадете механизъм за известяване, който да ви уведомява, ако има дубликати за конкретна таблица (разбира се, след като сте внедрили малко автоматизация с помощта на този инструмент), което е доста удобно.
Както при всичко, свързано със задачи на DBA, не забравяйте винаги да тествате всичко в среда на пясъчна среда, преди да натиснете спусъка в производството. И когато го направите, уверете се, че имате резервно копие на таблицата, върху която се фокусирате.