-- 完全重復表
CREATE TABLE dup_all (c1 INT, c2 INT, c3 VARCHAR(100));
INSERT INTO dup_all VALUES
(1,100,'aaa'),(1,100,'aaa'),(1,100,'aaa'),(1,100,'aaa'),(1,100,'aaa'),
(2,200,'bbb'),(3,300,'ccc'),(4,400,'ddd'),(5,500,'eee');
-- 部分重復表(有主鍵)
CREATE TABLE dup_col (c1 INT PRIMARY KEY, c2 INT, c3 VARCHAR(100));
INSERT INTO dup_col VALUES
(1,100,'aaa'),(2,100,'aaa'),(3,100,'aaa'),(4,100,'aaa'),(5,500,'eee');
SELECT DISTINCT * INTO #tmp FROM dup_all WHERE c1 = 1;
DELETE dup_all WHERE c1 = 1;
INSERT INTO dup_all SELECT * FROM #tmp;
DROP TABLE #tmp;
優點:簡單直觀。缺點:大表鎖表久、產生大量日志。
-- 完全重復
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY c1,c2,c3 ORDER BY (SELECT 0)) AS rn
FROM dup_all WHERE c1 = 1
)
DELETE cte WHERE rn > 1;
-- 部分重復(按 c2,c3 分組,保留 c1 最小)
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY c2,c3 ORDER BY c1) AS rn
FROM dup_col
)
DELETE cte WHERE rn > 1;
優點:單語句、可指定保留策略(最小/最大/最新)、大表高效。
CREATE TABLE tmp (
c1 INT, c2 INT, c3 VARCHAR(100),
CONSTRAINT UQ_tmp UNIQUE (c2,c3) WITH (IGNORE_DUP_KEY = ON)
);
INSERT INTO tmp SELECT * FROM dup_col; -- 重複行自動忽略,只保留首行
SELECT * FROM tmp; -- 結果:c1=1,2,5 三行
⚠️ 注意:IGNORE_DUP_KEY 僅對 INSERT 生效,UPDATE 仍報錯;SQL Server 2016+ 建議改用 ROW_NUMBER。
-- 保留每組 c1 最小
DELETE FROM dup_col
WHERE c1 NOT IN (SELECT MIN(c1) FROM dup_col GROUP BY c2, c3);
-- 或 EXISTS 寫法(通常性能更好)
DELETE FROM dup_col d1
WHERE EXISTS (
SELECT 1 FROM dup_col d2
WHERE d2.c2 = d1.c2 AND d2.c3 = d1.c3 AND d2.c1 < d1.c1
);
SELECT * INTO new_table FROM table1
UNION -- 自動去重
SELECT * FROM table2
UNION
SELECT * FROM table3;
| 場景 | 推薦方法 | 理由 |
|---|---|---|
| 小表(<1萬行)、一次性清理 | 臨時表 / DISTINCT | 代碼簡單、無版本依賴 |
| 大表、生產環境、需保留特定行 | ROW_NUMBER + CTE | 單掃描、可控保留邏輯、並行執行 |
| 歷史數據一次性載入目標表 | IGNORE_DUP_KEY 索引 | 載入過程自動去重、無需額外步驟 |
| 跨表合併去重 | UNION / UNION ALL + ROW_NUMBER | 集合運算天然去重 |
DBCC CHECKCONSTRAINTS 檢測約束違規。