資料表中出現重複列是資料庫維護常見議題。本文整理四種主流刪除重複資料的 SQL 方案,並說明適用情境與效能考量。
核心邏輯:將不重複資料暫存,清空原表,再寫回。
-- 1. 找出重複鍵值
SELECT key_col, COUNT(*) INTO #temp_dup FROM source_tbl GROUP BY key_col HAVING COUNT(*) > 1;
-- 2. 找出不重複鍵值
INSERT INTO #temp_dup
SELECT key_col, COUNT(*) FROM source_tbl GROUP BY key_col HAVING COUNT(*) = 1;
-- 3. 保留所有不重複資料
SELECT * INTO #temp_unique FROM source_tbl WHERE key_col IN (SELECT key_col FROM #temp_dup);
-- 4. 清空原表
DELETE FROM source_tbl;
-- 5. 寫回唯一資料
INSERT INTO source_tbl SELECT * FROM #temp_unique;
-- 6. 清理暫存表
DROP TABLE #temp_dup, #temp_unique;
優點:邏輯清晰,易理解維護。
缺點:大量資料時鎖表時間長,影響併發。
DECLARE @max INT, @id INT;
DECLARE cur CURSOR LOCAL FOR
SELECT key_col, COUNT(*) FROM source_tbl GROUP BY key_col HAVING COUNT(*) > 1;
OPEN cur;
FETCH NEXT FROM cur INTO @id, @max;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @max = @max - 1; -- 保留 1 筆
SET ROWCOUNT @max; -- 限制影響筆數
DELETE FROM source_tbl WHERE key_col = @id;
FETCH NEXT FROM cur INTO @id, @max;
END
CLOSE cur;
DEALLOCATE cur;
SET ROWCOUNT 0;
關鍵:SET ROWCOUNT n 讓後續 DELETE 只刪前 n 筆,確保每組至少保留一筆。
注意:SQL Server 2012+ 建議改用 TOP (n) 語法,SET ROWCOUNT 未來版本將移除。
CREATE PROCEDURE usp_RemoveDuplicates
@table_name VARCHAR(100),
@key_col VARCHAR(100)
AS
BEGIN
DECLARE @sql NVARCHAR(MAX), @id VARCHAR(100), @max INT, @type INT;
-- 動態建立游標查重複鍵
SET @sql = N'DECLARE cur CURSOR FOR SELECT ' + QUOTENAME(@key_col) + ', COUNT(*)
FROM ' + QUOTENAME(@table_name) + ' GROUP BY ' + QUOTENAME(@key_col) + ' HAVING COUNT(*) > 1';
EXEC sp_executesql @sql;
OPEN cur;
FETCH NEXT FROM cur INTO @id, @max;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @max = @max - 1;
SET ROWCOUNT @max;
-- 依鍵值資料型別動態建構刪除語句
SELECT @type = xtype FROM syscolumns WHERE id = OBJECT_ID(@table_name) AND name = @key_col;
IF @type = 56 -- INT
SET @sql = N'DELETE FROM ' + QUOTENAME(@table_name) + ' WHERE ' + QUOTENAME(@key_col) + ' = ' + @id;
ELSE IF @type = 167 -- VARCHAR
SET @sql = N'DELETE FROM ' + QUOTENAME(@table_name) + ' WHERE ' + QUOTENAME(@key_col) + ' = ''' + @id + '''';
EXEC sp_executesql @sql;
FETCH NEXT FROM cur INTO @id, @max;
END
CLOSE cur; DEALLOCATE cur; SET ROWCOUNT 0;
END;
適用:需跨多張表、多種鍵型別通用刪重邏輯時。
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY key_col ORDER BY (SELECT 0)) AS rn
FROM source_tbl
)
DELETE FROM cte WHERE rn > 1;
優點:語法簡潔、效能最佳、無需暫存表或游標、單陳述式完成。
建議:新專案優先採用此法;舊版本 SQL Server 才考慮前三種方案。
| 方案 | 適用版本 | 資料量建議 | 維護性 | 效能 |
|---|---|---|---|---|
| 暫存表法 | 所有版本 | < 10 萬筆 | 高 | 中 |
| 游標+ROWCOUNT | 所有版本 | > 100 萬筆 | 中 | 低(逐組提交) |
| 動態預存程序 | 所有版本 | 中等 | 高(可重用) | 中 |
| ROW_NUMBER() | SQL 2005+ | 所有量級 | 最高 | 最高 |