缺乏主鍵/唯一約束的資料表在大量匯入、合併、同步時極易產生重複列。重複資料不僅浪費儲存空間,更會導致彙總統計錯誤、應用邏輯異常。本文以 employee 表為例,系統整理從查詢識別到刪除清理的完整方案集。
-- 表結構
CREATE TABLE employee (
emp_id NUMBER(10),
emp_name VARCHAR2(20),
salary NUMBER(10,2)
);
-- 範例資料 (含重複)
emp_id | emp_name | salary
-------|----------|-------
1 | sunshine | 10000
1 | sunshine | 10000 -- 重複
2 | semon | 20000
2 | semon | 20000 -- 重複
3 | xyz | 30000
2 | semon | 20000 -- 重複 (第三筆)
-- 1. 建立暫存表保留唯一值
CREATE TABLE employee_dedup AS
SELECT DISTINCT * FROM employee;
-- 2. 刪除原表並重命名 (需注意權限、索引、觸發器、外鍵重建)
DROP TABLE employee;
RENAME employee_dedup TO employee;
-- 之後需重建 PK、索引、授權...
優點: 邏輯簡單、無需複雜 SQL。
缺點: 鎖表時間長、需重建相依物件、不適用大表線上環境。
-- 查詢重複的鍵值組合
SELECT emp_id, emp_name, salary, COUNT(*) AS dup_cnt
FROM employee
GROUP BY emp_id, emp_name, salary
HAVING COUNT(*) > 1;
結果可作為後續刪除條件依據。
-- 保留每組最早插入的列 (MIN ROWID)
DELETE FROM employee e1
WHERE ROWID > (
SELECT MIN(ROWID)
FROM employee e2
WHERE e1.emp_id = e2.emp_id
AND e1.emp_name = e2.emp_name
AND e1.salary = e2.salary
);
-- 或保留最新 (MAX ROWID)
DELETE FROM employee e1
WHERE ROWID < (
SELECT MAX(ROWID)
FROM employee e2
WHERE e1.emp_id = e2.emp_id
AND e1.emp_name = e2.emp_name
AND e1.salary = e2.salary
);
原理: Oracle ROWID 編碼資料檔案/區塊/列位置,唯一標識物理列,單表掃描即可完成,效能極佳。
-- 1. 建立暫存表存唯一值
CREATE GLOBAL TEMPORARY TABLE tmp_employee_unique
ON COMMIT PRESERVE ROWS
AS SELECT DISTINCT * FROM employee;
-- 2. 清空原表 (TRUNCATE 速度快、不產生 UNDO、釋放高水位線)
TRUNCATE TABLE employee;
-- 3. 回寫唯一資料
INSERT INTO employee SELECT * FROM tmp_employee_unique;
-- 4. 重建索引、約束、授權...
適用: 大表、需保留表結構、可接受短暫鎖表的維護窗口。
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY emp_id, emp_name, salary ORDER BY emp_id) AS rn
FROM employee
)
DELETE FROM employee
WHERE (emp_id, emp_name, salary) IN (
SELECT emp_id, emp_name, salary FROM cte WHERE rn > 1
);
-- 或直接 DELETE FROM cte WHERE rn > 1 (支援可更新 CTE 的資料庫)
優點: 標準 SQL、可攜性強、邏輯清晰、支援複雜排序保留規則 (如保留最新修改時間)。
| 方法 | 適用資料庫 | 資料量 | 鎖表影響 | 相依物件處理 | 推薦指數 |
|---|---|---|---|---|---|
| DISTINCT 重建 | 所有 | < 10 萬列 | 高 (鎖表久) | 需全手動重建 | ★★ |
| ROWID 刪除 | Oracle | 任意 | 低 (列級鎖) | 自動保留 | ★★★★★ |
| 暫存表+TRUNCATE | 所有 | 大表 | 中 (截斷瞬間鎖) | 需手動重建索引/約束 | ★★★★ |
| CTE + ROW_NUMBER | PG/SQL Server/MySQL 8.0+ | 任意 | 低-中 | 自動保留 | ★★★★★ |
MERGE / UPSERT 邏輯,避免盲目 INSERT。