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å.