DISTINCT 僅能針對 SELECT 清單的所有欄位組合去重,無法直接「指定某欄位去重並保留其他欄位」。本文揭露常見誤區,提供 GROUP BY、視窗函數、聚合函數三種正確解法,並說明適用場景。
-- 表結構
id | name
---|-----
1 | a
2 | b
3 | c
4 | c
5 | b
SELECT DISTINCT name, id FROM t;
/* 結果:5 筆全保留,因 (name,id) 組合皆唯一 */
SELECT id, DISTINCT name FROM t; -- 語法錯誤,DISTINCT 必須緊接 SELECT
SELECT * FROM t WHERE DISTINCT name; -- 語法錯誤
-- 依 name 分組,取每組第一筆(MySQL 允許非聚合欄位不在 GROUP BY 中)
SELECT id, name FROM t GROUP BY name;
/* 結果:
id | name
---|-----
1 | a
2 | b
3 | c
*/
ONLY_FULL_GROUP_BY 模式(5.7+ 預設開啟)下需將非聚合欄位加入 GROUP BY 或用聚合函數:SELECT MIN(id), name FROM t GROUP BY name;WITH ranked AS (
SELECT id, name,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY id) AS rn
FROM t
)
SELECT id, name FROM ranked WHERE rn = 1;
/* 精確取每組最小 id 的那一筆,符合 ONLY_FULL_GROUP_BY */
ORDER BY id DESC 取最新)。SELECT *, COUNT(DISTINCT name) FROM t GROUP BY name;
/* 產生多一欄計數,但達到去重目的 */
COUNT(DISTINCT name) 強制分組,副作用是多出一欄。GROUP_CONCAT、無視窗函數時的權宜之計。-- 取每個 name 對應的最大 id 及其建立時間
SELECT name, MAX(id) AS max_id, MAX(created_at) AS latest_time
FROM t
GROUP BY name;
| 方法 | 適用版本 | 精確度 | 效能 | 備註 |
|---|---|---|---|---|
| GROUP BY | 所有 | 取任意一筆(非確定) | 快 | 舊版預設行為;新版需配合 ONLY_FULL_GROUP_BY |
| ROW_NUMBER() | 8.0+ | 完全可控 | 中(需排序) | 標準寫法,推薦新專案 |
| 聚合函數欺騙 | 所有 | 取任意一筆 | 快 | 產生多餘欄位,語意不清 |
| JOIN 子查詢 | 所有 | 可控 | 中 | SELECT * FROM t JOIN (SELECT name, MIN(id) AS id FROM t GROUP BY name) x USING (name,id) |
ROW_NUMBER() 視窗函數,語意清晰、標準相容。GROUP BY 搭配 MIN()/MAX() 確定取值,或關閉 ONLY_FULL_GROUP_BY(不建議)。UNIQUE KEY + ON DUPLICATE KEY UPDATE 寫入端去重,或維護物化視圖/摘要表。理解 DISTINCT 作用於「整列組合」而非「單欄位」,選擇適合版本與業務精確度的去重方案,才能寫出正確且高效的 SQL。