「每個分類下價格最低的商品」、「每個用戶最近 5 條訂單」、「每個玩家最高分」——這類 **Top-N per Group** 問題在業務中極常見。本文以水果價格表為例,詳解 5 種主流寫法、適用場景與效能特徵。
type | variety | price
--------|---------|------
apple | gala | 2.5
apple | fuji | 3.0
orange | navel | 3.5
orange | valencia| 4.0
pear | bartlett| 2.0
cherry | bing | 5.0
cherry | rainier | 6.0
SELECT f.type, f.variety, f.price
FROM fruits f
INNER JOIN (
SELECT type, MIN(price) AS min_price
FROM fruits
GROUP BY type
) x ON f.type = x.type AND f.price = x.min_price;
缺點:僅能取最小值(Top 1),擴展到 Top N 需疊加子查詢,SQL 膨脹嚴重。
SELECT type, variety, price
FROM fruits f1
WHERE price = (
SELECT MIN(price) FROM fruits f2 WHERE f2.type = f1.type
);
Top 2 寫法:
SELECT type, variety, price
FROM fruits f1
WHERE price = (SELECT MIN(price) FROM fruits f2 WHERE f2.type = f1.type)
OR price = (
SELECT MIN(price) FROM fruits f3
WHERE f3.type = f1.type
AND price > (SELECT MIN(price) FROM fruits f4 WHERE f4.type = f1.type)
);
缺點:N 越大 SQL 指數級膨脹,執行計劃差,O(N²) 複雜度,不推薦生產使用。
SELECT type, variety, price
FROM fruits f1
WHERE (
SELECT COUNT(*)
FROM fruits f2
WHERE f2.type = f1.type
AND f2.price <= f1.price
) <= 2; -- 這裡改數字即可支援 Top N
原理:對外層每行,統計同組內「價格 ≤ 當前行」的行數,若 ≤ N 則屬於 Top N。
優點:單條 SQL 支援任意 N,邏輯清晰。
缺點:相關子查詢在某些優化器下執行計劃不佳,大數據量需測試。
(SELECT * FROM fruits WHERE type = 'apple' ORDER BY price LIMIT 2)
UNION ALL
(SELECT * FROM fruits WHERE type = 'orange' ORDER BY price LIMIT 2)
UNION ALL
(SELECT * FROM fruits WHERE type = 'pear' ORDER BY price LIMIT 2)
UNION ALL
(SELECT * FROM fruits WHERE type = 'cherry' ORDER BY price LIMIT 2);
適用條件:
(type, price) 上有複合索引關鍵點:必須用 UNION ALL 而非 UNION,避免額外去重排序開銷。
SET @num := 0, @type := '';
SELECT type, variety, price
FROM (
SELECT
type, variety, price,
@num := IF(@type = type, @num + 1, 1) AS row_number,
@type := type AS dummy
FROM fruits
ORDER BY type, price
) x
WHERE x.row_number <= 2;
type, price 排序row_number <= 2優勢:單次全表掃描,無相關子查詢,O(N) 線性複雜度,大數據量效能最優。
注意:MySQL 8.0+ 建議改用窗口函數 ROW_NUMBER() OVER (PARTITION BY type ORDER BY price)。
| 方法 | 複雜度 | 適用場景 | MySQL 版本 | 推薦度 |
|---|---|---|---|---|
| 自連接 | O(N log N) | Top 1,簡單查詢 | 所有 | ⭐⭐⭐ |
| 相關子查詢 | O(N²) | 教學演示,極小數據 | 所有 | ⭐ |
| 計數法 | O(N²) 優化器依賴 | 通用 Top N,中小表 | 所有 | ⭐⭐⭐ |
| UNION ALL | O(N log N) / 組 | 固定分組、有索引、組數少 | 所有 | ⭐⭐⭐⭐ |
| 用戶變量 | O(N) | 大表、動態分組、MySQL 5.7/8.0 | 5.7+ | ⭐⭐⭐⭐⭐ |
| 窗口函數 | O(N log N) | 現代標準寫法,可讀性最佳 | 8.0+ | ⭐⭐⭐⭐⭐ |
WITH ranked AS (
SELECT
type, variety, price,
ROW_NUMBER() OVER (PARTITION BY type ORDER BY price) AS rn
FROM fruits
)
SELECT type, variety, price
FROM ranked
WHERE rn <= 2;
優勢:標準 SQL、可讀性強、優化器支援最好、支援複雜分區排序。
掌握多種寫法並理解其效能特徵,能根據業務數據量、MySQL 版本、索引狀況做出最優選擇。