Đôi khi trong quá trình chạy DBA, chúng tôi gặp ít nhất một bảng được tải với các bản ghi trùng lặp. Ngay cả khi bảng có Khóa chính (khóa tự động tăng dần trong hầu hết các trường hợp), phần còn lại của các trường có thể có giá trị trùng lặp.
Tuy nhiên, SQL Server cho phép nhiều cách để loại bỏ các bản ghi trùng lặp đó (ví dụ:sử dụng CTE, hàm Xếp hạng SQL, truy vấn con với Nhóm Theo, v.v.).
Tôi nhớ một lần, trong một cuộc phỏng vấn, tôi được hỏi làm thế nào để xóa các bản ghi trùng lặp trong một bảng trong khi chỉ để lại 1 bản ghi trong số đó. Vào thời điểm đó, tôi không thể trả lời, nhưng tôi rất tò mò. Sau khi nghiên cứu một chút, tôi đã tìm thấy nhiều tùy chọn để giải quyết vấn đề này.
Bây giờ, nhiều năm sau, tôi ở đây để trình bày cho bạn Quy trình đã lưu trữ nhằm mục đích trả lời câu hỏi “làm thế nào để xóa các bản ghi trùng lặp trong bảng SQL?”. Bất kỳ DBA nào cũng có thể đơn giản sử dụng nó để thực hiện một số công việc dọn dẹp nhà cửa mà không cần lo lắng quá nhiều.
Tạo thủ tục được lưu trữ:Cân nhắc ban đầu
Tài khoản bạn sử dụng phải có đủ đặc quyền để tạo Quy trình được lưu trữ trong cơ sở dữ liệu dự định.
Tài khoản thực hiện Thủ tục được lưu trữ này phải có đủ đặc quyền để thực hiện các hoạt động CHỌN và XÓA đối với bảng cơ sở dữ liệu đích.
Thủ tục được lưu trữ này dành cho các bảng cơ sở dữ liệu không có Khóa chính (cũng như ràng buộc DUY NHẤT) được xác định. Tuy nhiên, nếu bảng của bạn có Khóa chính, thì Quy trình đã lưu trữ sẽ không tính đến các trường đó. Nó sẽ thực hiện việc tra cứu và xóa dựa trên phần còn lại của các trường (vì vậy hãy sử dụng nó thật cẩn thận trong trường hợp này).
Cách sử dụng quy trình đã lưu trữ trong SQL
Sao chép và dán Mã SP T-SQL có sẵn trong bài viết này. SP mong đợi 3 tham số:
@schemaName - tên của lược đồ bảng cơ sở dữ liệu nếu áp dụng. Nếu không - hãy sử dụng dbo .
@tableName - tên của bảng cơ sở dữ liệu nơi các giá trị trùng lặp được lưu trữ.
@displayOnly - nếu được đặt thành 1 , các bản ghi thực tế trùng lặp sẽ không bị xóa , nhưng chỉ hiển thị thay thế (nếu có). Theo mặc định, giá trị này được đặt thành 0 nghĩa là việc xóa thực sự sẽ xảy ra nếu bản sao tồn tại.
Thủ tục lưu trữ trên máy chủ SQL Kiểm tra thực thi
Để chứng minh Quy trình đã lưu trữ, tôi đã tạo hai bảng khác nhau - một bảng không có Khóa chính và một bảng có Khóa chính. Tôi đã chèn một số bản ghi giả vào các bảng này. Hãy kiểm tra xem tôi nhận được kết quả nào trước / sau khi thực hiện Quy trình đã lưu trữ.
Bảng SQL có khóa chính
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
Thủ tục lưu trữ SQL Bản ghi mẫu
INSERT INTO test VALUES('A','A',1),('A','B',1),('A','C',1),('B','A',2),('B','B',3),('B','C',4)
Thực thi quy trình đã lưu trữ chỉ với màn hình
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 1
Vì cột1 và cột2 tạo thành Khóa chính, các bản sao được đánh giá dựa trên các cột không phải là Khóa chính, trong trường hợp này là cột 3. Kết quả là đúng.
Thực thi quy trình đã lưu trữ mà không chỉ hiển thị
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 0
Các bản ghi trùng lặp đã biến mất.
Tuy nhiên, bạn phải cẩn thận với cách tiếp cận này vì lần xuất hiện đầu tiên của bản ghi là lần sẽ bị cắt. Vì vậy, nếu vì bất kỳ lý do gì bạn cần xóa một bản ghi cụ thể, thì bạn phải giải quyết trường hợp cụ thể của mình một cách riêng biệt.
SQL Bảng không có khóa chính
CREATE TABLE [dbo].[duplicates](
[column1] [varchar](16) NOT NULL,
[column2] [varchar](16) NOT NULL,
[column3] [varchar](16) NOT NULL
) ON [PRIMARY]
GO
Thủ tục lưu trữ SQL Bản ghi mẫu
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')
Thực thi quy trình đã lưu trữ chỉ với màn hình
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 1
Kết quả đầu ra là chính xác, đó là các bản ghi trùng lặp trong bảng.
Thực thi quy trình đã lưu trữ mà không chỉ hiển thị
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 0
Quy trình đã lưu trữ đã hoạt động như mong đợi và các bản sao được làm sạch thành công.
Trường hợp đặc biệt cho Thủ tục được lưu trữ này trong SQL
Nếu lược đồ hoặc bảng bạn đang chỉ định không tồn tại trong cơ sở dữ liệu của bạn, thì Thủ tục đã lưu trữ sẽ thông báo cho bạn và tập lệnh sẽ kết thúc quá trình thực thi của nó.
Nếu bạn để trống tên lược đồ, tập lệnh sẽ thông báo cho bạn và kết thúc quá trình thực thi của nó.
Nếu bạn để trống tên bảng, tập lệnh sẽ thông báo cho bạn và kết thúc quá trình thực thi của nó.
Nếu bạn thực thi Quy trình đã lưu trữ đối với bảng không có bất kỳ bản sao nào và kích hoạt bit @displayOnly , bạn sẽ nhận được một tập hợp kết quả trống.
Quy trình lưu trữ trên máy chủ SQL:Mã hoàn chỉnh
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
Kết luận
Nếu bạn không biết cách xóa các bản ghi trùng lặp trong bảng SQL, thì các công cụ như thế này sẽ hữu ích cho bạn. Bất kỳ DBA nào cũng có thể kiểm tra xem có bảng cơ sở dữ liệu nào không có Khóa chính (cũng như ràng buộc Duy nhất) cho chúng hay không, điều này có thể tích lũy một đống bản ghi không cần thiết theo thời gian (có khả năng lãng phí bộ nhớ). Chỉ cần cắm và chạy Quy trình đã lưu trữ và bạn đã sẵn sàng.
Bạn có thể đi xa hơn một chút và xây dựng một cơ chế cảnh báo để thông báo cho bạn nếu có bản sao cho một bảng cụ thể (tất nhiên là sau khi triển khai một chút tự động hóa bằng công cụ này), điều này khá tiện dụng.
Như với bất kỳ thứ gì liên quan đến các tác vụ DBA, hãy đảm bảo luôn kiểm tra mọi thứ trong môi trường hộp cát trước khi kích hoạt trong quá trình sản xuất. Và khi bạn làm như vậy, hãy đảm bảo có một bản sao lưu của bảng mà bạn tập trung vào.