max() over 不能直接获取关联字段值,因其仅返回字典序或数值最大值,不绑定原始行上下文;正确做法是用first_value(name) over (partition by category order by price desc)或row_number()配合过滤。

MAX() OVER 为什么不能直接拿到关联字段的值?
很多人写 MAX(name) OVER (PARTITION BY category) 想拿到“销量最高那条记录对应的 name”,但 SQL 会报错或返回意外结果——因为 MAX() 是聚合函数,只对数值/字符串做字典序最大值,不保留原始行上下文。它不会“记住”哪个 name 和最大 price 出现在同一行。
正确做法:用 ROW_NUMBER() 或 FIRST_VALUE() 配合 ORDER BY
真正要的是“分组内按某列排序后,取第一行的某个字段”,不是“取该字段的最大值”。这时该用窗口函数保持行粒度:
-
FIRST_VALUE(name) OVER (PARTITION BY category ORDER BY price DESC)—— 最简洁,但注意默认ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,可能因重复price返回非预期行;加ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING更稳妥 -
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC, id)—— 加上id或其他唯一列防并列,再用外层WHERE rn = 1过滤(适合 SELECT 全字段) - 避免用
RANK()或DENSE_RANK()直接取 top 1,它们会在并列时都返回 1,导致多行
性能和 NULL 处理的坑
FIRST_VALUE() 在大表 + 多分区场景下,若未建合适索引(如 (category, price) 联合索引),排序开销明显;另外,如果 price 有 NULL,ORDER BY price DESC 会让 NULL 排最前(多数数据库默认),导致取到空值行。解决方法:
- 显式控制
NULL位置:ORDER BY price DESC NULLS LAST(PostgreSQL / Oracle 支持) - 兼容 MySQL 8.0+:
ORDER BY IFNULL(price, -9999999) DESC - 确认执行计划里是否走了索引扫描,而不是全表排序
MySQL 5.7 或旧版本没窗口函数怎么办?
只能用自连接或相关子查询,但性能差、语法冗长。例如找每个 category 下 price 最高的 name:
SELECT t1.category, t1.name FROM products t1 WHERE t1.price = ( SELECT MAX(t2.price) FROM products t2 WHERE t2.category = t1.category );
注意:这个写法在有多个相同最大 price 时会返回多行;如果只要一行,得额外加 LIMIT 1 并改用派生表,逻辑立刻变重。升级到 MySQL 8.0+ 用 FIRST_VALUE() 是更干净的选择。
ORDER BY 子句里没处理 NULL 和并列情况,导致线上数据偶尔错乱,而且难以复现。










