在數據清洗、主鍵建立、業務合規檢查等場景中,「查詢某欄位重複值的記錄」是高頻需求。本文以學生表(student)為例,對比兩種經典寫法的執行計畫與實測效能,並給出生產環境推薦方案。
studentSno(學號,擬設為主鍵)、ID(身份證,唯一業務鍵)Sno 存在重複Sno 重複的完整記錄SELECT *
FROM student AS A
WHERE Sno IN (
SELECT Sno
FROM student AS B
WHERE A.ID <> B.ID
);
A,內層子查詢在表 B 中尋找「同學號但不同身份證」的記錄。A.ID <> B.ID 確保不匹配自身,等價於「存在至少另一筆相同 Sno」。Sno 索引可達線性掃描複雜度。SELECT *
FROM student AS A
WHERE (SELECT COUNT(*) FROM student WHERE Sno = A.Sno) > 1;
COUNT(*)。| 指標 | 方法一 (IN + 自連接) | 方法二 (COUNT 子查詢) |
|---|---|---|
| 執行時間 | ~2 秒 | ~4 分 30 秒 |
| 邏輯讀取 | ~30,000 頁 | ~1.2 億頁 |
| CPU 時間 | ~150 ms | ~180,000 ms |
| 執行計畫 | Hash Match (Right Semi Join) | Nested Loops + Table Spool |
WITH DupCTE AS (
SELECT
*,
COUNT(*) OVER (PARTITION BY Sno) AS Cnt
FROM student
)
SELECT * FROM DupCTE WHERE Cnt > 1;
-- 保留每組 Sno 的最小 ID,刪除其餘
WITH Ranked AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY Sno ORDER BY ID) AS RN
FROM student
)
DELETE FROM student
WHERE ID IN (SELECT ID FROM Ranked WHERE RN > 1);
-- 針對去重查詢建立覆蓋索引
CREATE INDEX IX_student_Sno_ID ON student (Sno) INCLUDE (ID, 其他常用列);
ORDER BY Sno 流式聚合。WHERE 中對 Sno 做函數運算(如 LTRIM(RTRIM(Sno))),導致索引失效。| 寫法 | 適用版本 | 效能 | 推薦等級 |
|---|---|---|---|
| IN + 自連接 (<>) | 所有版本 | ⭐⭐⭐⭐⭐ | ✅ 生產首選 |
| 窗口函數 COUNT(*) OVER | SQL Server 2005+ / PG / MySQL 8+ | ⭐⭐⭐⭐⭐ | ✅ 現代首選 |
| EXISTS + 子查詢 | 所有版本 | ⭐⭐⭐⭐ | ✅ 可選 |
| GROUP BY HAVING COUNT(*) > 1 (再 JOIN 回表) | 所有版本 | ⭐⭐⭐ | ⚠️ 需二次掃描 |
| 相關子查詢 COUNT(*) | 所有版本 | ⭐ (極差) | ❌ 禁用 |
下次面對「找重複」需求,請優先選擇 窗口函數 或 IN + 自連接,並配合適當索引,讓百萬級數據也能秒級出結果。