經典順序表去重:建立新表 Lb,將 La 第一元素放入,後續元素逐一與 Lb 比對,不重複則追加。時間複雜度 O(n²),資料庫中對應「建立暫存表 → DISTINCT → 覆寫」模式。
SELECT DISTINCT * INTO #Tmp FROM SourceTable;
DROP TABLE SourceTable;
SELECT * INTO SourceTable FROM #Tmp;
DROP TABLE #Tmp;
⚠️ 會導致索引、約束、權限、觸發器遺失,需事後重建。根治:加上唯一索引/主鍵防止重複產生。
-- 相關子查詢法
SELECT 序號 = (SELECT COUNT(*) FROM Customer a WHERE a.CustId <= b.CustId), CustId, Company
FROM Customer b ORDER BY 1;
-- 自連接分組法
SELECT 序號 = COUNT(*), a.CustId, a.Company
FROM Customer a, Customer b
WHERE a.CustId >= b.CustId
GROUP BY a.CustId, a.Company ORDER BY 序號;
-- RANK() 允許並列
SELECT RANK() OVER (ORDER BY CustId DESC) AS Seq, CustId, Company FROM Customer;
-- ROW_NUMBER() 唯一序號,配合 CTE 刪除重複
WITH CTE AS (
SELECT ROW_NUMBER() OVER (PARTITION BY CustId, Company ORDER BY CustId) AS RN
FROM Customer
)
DELETE FROM CTE WHERE RN > 1;
每列唯一 `ROWID` 編碼位置。刪除重複保留最大 ROWID:
DELETE FROM Test a
WHERE a.ROWID != (SELECT MAX(ROWID) FROM Test b WHERE a.Id = b.Id);
DECLARE @Max INT, @Id INT;
DECLARE Cur CURSOR LOCAL FOR
SELECT Id, COUNT(*) FROM Test GROUP BY Id 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 Test WHERE Id = @Id;
FETCH NEXT FROM Cur INTO @Id, @Max;
END
CLOSE Cur; DEALLOCATE Cur;
SET ROWCOUNT 0;
WITH Ranked AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Col1, Col2 ORDER BY (SELECT 0)) AS RN
FROM SourceTable
)
DELETE FROM SourceTable
WHERE EXISTS (SELECT 1 FROM Ranked r WHERE r.Col1 = SourceTable.Col1 AND r.Col2 = SourceTable.Col2 AND r.RN > 1);
| 場景 | 推薦方法 | 備註 |
|---|---|---|
| 一次性清理、資料量中等 | `SELECT DISTINCT INTO #Tmp` + 重建 | 最簡單,需重建索引/約束 |
| 生產環境定期去重、有主鍵/唯一鍵 | `ROW_NUMBER() OVER (PARTITION BY ...)` + `DELETE RN>1` | 標準 SQL、效能佳、可精確控制保留邏輯 |
| 無主鍵、舊版 SQL Server | 自連接構造序號或游標 | 效能差,僅作備選 |
| Oracle | `ROWID` 刪除法 | 原生支援、極快 |
| 防止重複產生 | **加唯一索引/主鍵/約束** | 源頭治理,一勞永逸 |
去重是治標,**唯一約束**才是治本。資料庫設計階段即應識別業務鍵並建立約束,避免事後清理成本。