問題定義
資料表無主鍵/唯一約束,導致出現**完全重複列**(所有欄位值皆相同)。需刪除重複列,每組僅保留第一筆。
解法:暫存表 + IDENTITY 排序
/* 1. 建立測試資料 */
CREATE TABLE dbo.testtab (id NCHAR(10) NULL, name NCHAR(10) NULL);
INSERT INTO testtab VALUES ('1','1'), ('1','1'), ('2','2'), ('2','2'), ('3','3'), ('3','3');
/* 2. 將資料複製至暫存表並加上流水號 */
SELECT IDENTITY(INT, 1, 1) AS autoID, *
INTO #Tmp
FROM testtab;
/* 3. 刪除暫存表中每組重複資料的最後一筆(保留第一筆) */
DELETE #Tmp
WHERE autoID IN (
SELECT MAX(autoID)
FROM #Tmp
GROUP BY id, name -- 依所有業務欄位分組
);
/* 4. 清空原表並寫回去重後資料 */
DELETE testtab;
INSERT INTO testtab (id, name)
SELECT id, name FROM #Tmp;
/* 5. 清理 */
DROP TABLE #Tmp;
/* 6. 驗證 */
SELECT * FROM testtab; -- 僅剩 3 筆
關鍵點解析
| 步驟 | 目的 |
IDENTITY(INT,1,1) | 為每列產生唯一遞增序號,作為「先後順序」依據。 |
GROUP BY id, name | 定義「重複」的判斷鍵,**必須包含所有業務欄位**。 |
MAX(autoID) | 取得每組最後寫入的序號(假設先寫入者為第一筆)。 |
INTO #Tmp | 避免在原表直接操作導致鎖定升級或觸發器副作用。 |
替代方案比較
| 方法 | 適用版本 | 優點 | 缺點 |
| ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) = 1 | 2005+ | 單一陳述式、可保留任意順序、無需暫存表 | 需支援視窗函數 |
| CTE + DELETE | 2005+ | 可讀性佳 | 同左 |
| 暫存表 + IDENTITY (本文) | 2000+ | 相容舊版、邏輯直觀 | 需兩階段、鎖定較久 |
| ALTER IGNORE TABLE ADD UNIQUE (舊 MySQL) | MySQL | 一指令 | SQL Server 不支援 |
現代寫法(SQL Server 2005+ 推薦)
WITH cte AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY id, name ORDER BY (SELECT 0)) AS rn
FROM testtab
)
DELETE FROM cte WHERE rn > 1;
預防重複資料最佳實務
- **定義主鍵或唯一約束**:
ALTER TABLE testtab ADD CONSTRAINT PK_testtab PRIMARY KEY (id, name);
- **ETL 階段去重**:使用
SSIS/Azure Data Factory 的「排序/去重」元件。
- **MERGE UPSERT 模式**:
MERGE INTO target USING source ON target.pk = source.pk WHEN NOT MATCHED THEN INSERT ...