Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Thủ tục được lưu trữ để xóa bản ghi trùng lặp trong bảng SQL

Đô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.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Thống kê chờ Knee-Jerk:SOS_SCHEDULER_YIELD

  2. Cách tính toán sự khác biệt giữa hai ngày giờ trong T-SQL

  3. Xem thông số, nhúng và các tùy chọn RECOMPILE

  4. API REST Python với Flask, Connexion và SQLAlchemy - Phần 3

  5. Nối 3 bảng trong SQL