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