本文處理的「重複」非指整列資料完全相同,而是特定欄位(如 Title)值重複的情況。資料表結構範例:
HZT 表結構
ID int (主鍵)
Title nvarchar(50) (業務鍵,可能重複)
AddDate datetime
SELECT * FROM HZT
WHERE Title IN (
SELECT Title FROM HZT
GROUP BY Title
HAVING COUNT(*) > 1
);
此法會列出所有 Title 重複的完整列,包含每個群組的所有筆數。
SELECT * FROM HZT
WHERE ID IN (
SELECT MAX(ID) FROM HZT
GROUP BY Title
);
常用於預覽「保留哪一筆」的結果確認。
DELETE FROM HZT
WHERE Title IN (
SELECT Title FROM HZT
GROUP BY Title
HAVING COUNT(*) > 1
);
極度不建議:此指令會清除整個重複群組,導致該業務鍵資料全失。
DELETE FROM HZT
WHERE ID NOT IN (
SELECT MAX(ID) FROM HZT
GROUP BY Title
);
此為最通用、最安全的刪除策略,保留每群組最新(ID 最大)的一筆。
當資料有「大小關係」(如 ID、日期、版本號)時,提供 7 種等效寫法,開發者可依習慣與效能選擇:
-- 方法 1:EXISTS 相關子查詢(直觀易讀)
DELETE a FROM #T a
WHERE EXISTS (SELECT 1 FROM #T WHERE Name = a.Name AND ID < a.ID);
-- 方法 2:LEFT JOIN 找出非最小值
DELETE a FROM #T a
LEFT JOIN (SELECT MIN(ID) AS ID, Name FROM #T GROUP BY Name) b
ON a.Name = b.Name AND a.ID = b.ID
WHERE b.ID IS NULL;
-- 方法 3:NOT IN 子查詢
DELETE a FROM #T a
WHERE ID NOT IN (SELECT MIN(ID) FROM #T WHERE Name = a.Name);
-- 方法 4:群組後 NOT IN(需 ID 唯一)
DELETE a FROM #T a
WHERE ID NOT IN (SELECT MIN(ID) FROM #T GROUP BY Name);
-- 方法 5:計數法
DELETE a FROM #T a
WHERE (SELECT COUNT(1) FROM #T WHERE Name = a.Name AND ID < a.ID) > 0;
-- 方法 6:TOP 1 排序法
DELETE a FROM #T a
WHERE ID <> (SELECT TOP 1 ID FROM #T WHERE Name = a.Name ORDER BY ID);
-- 方法 7:ANY 比較運算子
DELETE a FROM #T a
WHERE ID > ANY (SELECT ID FROM #T WHERE Name = a.Name);
-- 核心邏輯:將所有「<」改為「>」,「MIN」改為「MAX」,「ASC」改為「DESC」
DELETE a FROM #T a
WHERE EXISTS (SELECT 1 FROM #T WHERE Name = a.Name AND ID > a.ID);
DELETE a FROM #T a
WHERE ID NOT IN (SELECT MAX(ID) FROM #T WHERE Name = a.Name);
DELETE a FROM #T a
WHERE ID <> (SELECT TOP 1 ID FROM #T WHERE Name = a.Name ORDER BY ID DESC);
DELETE a FROM #T a
WHERE ID < ANY (SELECT ID FROM #T WHERE Name = a.Name);
當資料完全相同(含所有欄位),無法用 ID 區分時:
SELECT DISTINCT * INTO #Temp FROM #T; -- 去重存入暫存表
TRUNCATE TABLE #T; -- 清空原表
INSERT INTO #T SELECT * FROM #Temp; -- 寫回去重後資料
ALTER TABLE #T ADD ID INT IDENTITY; -- 加入流水號
DELETE a FROM #T a
WHERE EXISTS (SELECT 1 FROM #T WHERE Num = a.Num AND Name = a.Name AND ID > a.ID);
ALTER TABLE #T DROP COLUMN ID; -- 移除流水號
DECLARE cur CURSOR LOCAL FOR
SELECT COUNT(1)-1, Num, Name FROM #T
GROUP BY Num, Name HAVING COUNT(1) > 1;
DECLARE @cnt INT, @num INT, @name NVARCHAR(1);
OPEN cur;
FETCH NEXT FROM cur INTO @cnt, @num, @name;
WHILE @@FETCH_STATUS = 0 BEGIN
SET ROWCOUNT @cnt; -- 限制刪除筆數
DELETE #T WHERE Num = @num AND Name = @name;
SET ROWCOUNT 0; -- 重置限制
FETCH NEXT FROM cur INTO @cnt, @num, @name;
END
CLOSE cur; DEALLOCATE cur;
| 場景 | 推薦方法 | 理由 |
|---|---|---|
| 一般業務表(有 PK、中小量資料) | NOT IN (SELECT MAX/MIN...) | 可讀性佳、優化器友善 |
| 大量資料(百萬筆以上) | EXISTS / LEFT JOIN | 避免 NOT IN 的 NULL 陷阱、效能穩定 |
| 無主鍵、完全重複列 | DISTINCT 暫存表 | 最簡單、鎖定時間最短 |
| 需保留特定業務邏輯(如最新/最舊) | 依排序欄位調整 MAX/MIN | 彈性最高 |
掌握這套完整方案,即能從容應對各類 SQL Server 重複資料清理需求。