「查詢某欄位不重複的記錄,同時保留其他欄位」是常見需求,但 DISTINCT 與 GROUP BY 的行為差異常導致誤用。本文透過實例拆解兩者機制,給出正確去重寫法。
-- 表結構:id, name
-- 資料:(1,'a'), (2,'b'), (3,'c'), (4,'c'), (5,'b')
SELECT DISTINCT name FROM t; -- ✅ 回傳 a,b,c
SELECT DISTINCT name, id FROM t; -- ❌ 回傳全部 5 筆(name+id 組合唯一)
SELECT id, DISTINCT name FROM t; -- ❌ 語法錯誤,DISTINCT 必須置於選項最前
核心規則:DISTINCT 作用於 整個選項列表的組合,而非單一欄位。無法「只對 name 去重、保留對應 id」。
MySQL 允許 GROUP BY 欄位未出現在選項中(非 ONLY_FULL_GROUP_BY 模式),且會自動取每組的第一筆記錄(非確定性,但常符合「取任一筆」需求)。
SELECT * FROM t GROUP BY name;
結果:(1,'a'), (2,'b'), (3,'c') —— 達到「name 不重複、保留其他欄位」目的。
-- 原查詢:LEFT JOIN 導致 nid 重複
SELECT n.nid, n.title, ni.thumbpath
FROM term_node tn
JOIN node n ON n.nid = tn.nid
JOIN node_images ni ON ni.nid = n.nid
WHERE tn.tid IN (...)
ORDER BY n.nid DESC;
-- 修正:GROUP BY 主表主鍵
SELECT n.nid, n.title, ni.thumbpath
FROM term_node tn
JOIN node n ON n.nid = tn.nid
JOIN node_images ni ON ni.nid = n.nid
WHERE tn.tid IN (...)
GROUP BY n.nid
ORDER BY n.nid DESC;
| 需求 | 推薦寫法 | 說明 |
|---|---|---|
| 只需統計不重複值的數量 | COUNT(DISTINCT col) | 標準 SQL,效能佳 |
| 需回傳去重欄位本身 | SELECT DISTINCT col | 語意清晰 |
| 需回傳去重欄位 + 其他欄位(單表) | SELECT * FROM t GROUP BY col | MySQL 擴展語法,簡潔 |
| 多表 JOIN 需以主表主鍵去重 | GROUP BY main_table.pk | 避免 JOIN 產生的重複行 |
| 嚴格 ANSI SQL 相容 / ONLY_FULL_GROUP_BY 模式 | 子查詢 / 窗口函數 ROW_NUMBER() | 標準寫法,MySQL 8.0+ 原生支援 |
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id) AS rn
FROM t
) x WHERE rn = 1;
此寫法語意明確、可控制保留哪一筆(如最新/最舊),為現代應用首選。
GROUP BY 必須置於 ORDER BY、LIMIT 之前,否則語法錯誤。sql_mode=ONLY_FULL_GROUP_BY(MySQL 5.7+ 預設開啟)時,SELECT * GROUP BY col 會報錯,需改用窗口函數或聚合函數。DISTINCT 可搭配聚合函數:COUNT(DISTINCT col),但無法搭配 WHERE 直接過濾。單表簡單去重用 GROUP BY 最快;多表 JOIN 去重以主表主鍵 GROUP BY;追求標準相容與可控保留邏輯,升級 MySQL 8.0 使用 ROW_NUMBER()。別再讓 DISTINCT 誤導你的查詢設計。