用窗口函数row_number()取每组最大值对应完整行最稳妥,即select from (select , row_number() over (partition by category order by score desc, id asc) as rn from scores) t where rn = 1;它能确保每组只返回一条稳定记录,避免rank()等导致多行或漏选。

用窗口函数 ROW_NUMBER() 取每组最大值对应完整行
直接 GROUP BY 加 MAX() 只能拿到最大值本身,拿不到该行其他字段(比如用户名、时间戳)。真正要“查出每个分组里最大值所在的那条完整记录”,得靠窗口函数。
核心思路:按分组排序,把最大值所在行标为第 1 名,再过滤出来。
-
ORDER BY value DESC确保最大值排最前(注意DESC,升序会拿最小值) - 同一组内多个值相等时,
ROW_NUMBER()会任意分配顺序,若需稳定结果,加二级排序,比如ORDER BY value DESC, id ASC - 别用
RANK()或DENSE_RANK()—— 它们对并列值给相同排名,会导致多行被选中
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn FROM scores ) t WHERE rn = 1;
WHERE ... IN (SELECT MAX(...)) 写法的隐患
这种写法看似直观,但实际容易漏数据或误匹配,尤其当分组内有重复最大值,且你想保留所有并列记录时——它只保证 score 是最大值,不保证整行唯一性。
- 如果
(category, score)不是联合唯一,WHERE (category, score) IN (...)可能命中多行,但逻辑上未必是你想要的“每组一条” - 子查询里没加
DISTINCT的MAX(score)在某些数据库(如 MySQL 5.7 严格模式)会报错 - 性能差:外层表每行都要跑一次子查询,没有索引支持时是 O(n²)
MySQL 8.0+ 和 PostgreSQL 支持 SELECT ... FROM ... LATERAL JOIN 吗?
不推荐强行用 LATERAL 替代窗口函数——它更适合关联子查询场景,而取分组极值是典型窗口函数用例。PostgreSQL 支持 LATERAL,但写起来更啰嗦;MySQL 8.0+ 根本不支持 LATERAL(截至 8.4),别白费劲。
- 真要用关联写法,不如用
JOIN+ 子查询聚合,但必须配ON条件防笛卡尔积 - 例如:
SELECT s.* FROM scores s JOIN (SELECT category, MAX(score) AS max_score FROM scores GROUP BY category) m ON s.category = m.category AND s.score = m.max_score - 这个写法能返回所有并列最大值记录,但无法控制“只取一条”的业务逻辑(比如要最新时间那条)
SQLite 和旧版 MySQL(
它们不支持窗口函数,只能退回到关联子查询或自连接。自连接更可控,也更容易加额外条件(比如“取创建时间最新的那条”)。
- 关键点:左表和右表同表别名,用
LEFT JOIN找“不存在更大值的记录” - 写法:
SELECT s1.* FROM scores s1 LEFT JOIN scores s2 ON s1.category = s2.category AND s1.score - 注意
而不是 <code>,否则会把并列最大值也排除掉 - 如果还要按时间去重,把
s1.score 换成 <code>(s1.score
窗口函数是目前最干净、可读性最强的解法,但得确认数据库版本;老系统上自连接虽然绕,但逻辑清晰、兼容性强。别为了“看起来高级”硬套不支持的语法,查不出数据比慢一点更糟。











