MySQL 中 GROUP BY 執行於 SELECT 之前、ORDER BY 之後,導致「分組後取最新記錄」的直觀寫法失效。本文透過執行順序分析、錯誤示範、三種正確解法完整解析。
| 書寫順序 | 執行順序 | 說明 |
|---|---|---|
| 1. SELECT | 5. SELECT | 投影欄位 |
| 2. FROM | 1. FROM | 確定數據源 |
| 3. WHERE | 2. WHERE | 行過濾 |
| 4. GROUP BY | 3. GROUP BY | 分組聚合 |
| 5. HAVING | 4. HAVING | 分組過濾 |
| 6. ORDER BY | 6. ORDER BY | 結果排序 |
| 7. LIMIT | 7. LIMIT | 限制行數 |
關鍵結論: ORDER BY 在 GROUP BY 之後執行,無法影響分組內「取哪一行」。
-- 錯誤:想取每個分類最新文章
SELECT * FROM test GROUP BY category_id ORDER BY `date` DESC;
-- 實際行為:
-- 1. GROUP BY 先執行,每組任意取一行 (通常是物理存儲第一行)
-- 2. ORDER BY 再對這些「任意行」排序
-- 結果:非各組最新記錄
SELECT * FROM (
SELECT * FROM `test` ORDER BY `date` DESC
) AS temp
GROUP BY category_id
ORDER BY `date` DESC;
原理: 內層先按時間倒序,外層 GROUP BY 取每組第一行 (即最新)。
適用: 中小數據量、邏輯簡單、MySQL 5.7 預設 ONLY_FULL_GROUP_BY 可能報錯需調整。
SELECT * FROM `test`
WHERE id IN (
SELECT SUBSTRING_INDEX(
GROUP_CONCAT(id ORDER BY `date` DESC), ',', 1
)
FROM `test`
GROUP BY category_id
)
ORDER BY `date` DESC;
原理:
GROUP_CONCAT(id ORDER BY date DESC):組內按時間串聯 IDSUBSTRING_INDEX(..., ',', 1):取第一個 ID (最新)IN 精確匹配注意: group_concat_max_len 預設 1024 字元,大組需調大:SET SESSION group_concat_max_len = 1000000;
SELECT t1.*
FROM `test` t1
JOIN (
SELECT category_id, MAX(`date`) AS max_date
FROM `test`
GROUP BY category_id
) t2 ON t1.category_id = t2.category_id AND t1.`date` = t2.max_date
ORDER BY t1.`date` DESC;
優勢:
(category_id, date) 複合索引ONLY_FULL_GROUP_BY 問題WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY `date` DESC) AS rn
FROM `test`
)
SELECT * FROM ranked WHERE rn = 1 ORDER BY `date` DESC;
優勢: 語義最清晰、效能優秀、支援並列處理 (RANK()/DENSE_RANK())。
| 方案 | 適用版本 | 效能 | 可讀性 | 推薦場景 |
|---|---|---|---|---|
| 子查詢先排序 | 5.0+ | 中 (臨時表) | 高 | 小表、快速開發 |
| GROUP_CONCAT | 5.0+ | 中低 (字串操作) | 中 | 中表、無窗口函數環境 |
| JOIN 聚合 | 全版本 | 高 (索引友好) | 高 | 大表、生產環境標準 |
| 窗口函數 | 8.0+ | 最高 | 最高 | 新專案、複雜分組邏輯 |
SELECT * FROM (
SELECT * FROM `ecm_copy_goods_change` ORDER BY `cid` DESC
) `ecm_copy_goods_change`
GROUP BY goods_id
ORDER BY `cid` DESC;