应使用min(salary) over(partition by dept_id)获取确定的分组最小值,而非first_value()配合order by,因后者在并列时返回不确定首行;max/min窗口函数需显式写over子句,否则退化为聚合函数。

用 MAX() 和 MIN() 窗口函数直接计算分组极值
不需要子查询或自连接,MAX() 和 MIN() 本身就能作为窗口函数使用。关键在于正确写 OVER(PARTITION BY ...),否则会默认按整张表计算。
- 错误写法:
MAX(salary)没加OVER→ 变成聚合函数,必须配GROUP BY - 正确写法:
MAX(salary) OVER (PARTITION BY dept_id)→ 每行返回其所在部门的最高薪资 -
PARTITION BY的字段必须是当前查询中能访问到的列(不能是计算列且未出现在SELECT或GROUP BY中) - 如果还希望按时间排序后取“最新一笔的最大值”,可以加上
ORDER BY create_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,但此时语义已变为累积最大值,不是静态分组极值
为什么 FIRST_VALUE() + ORDER BY 不等于分组最小值?
FIRST_VALUE(x) OVER (PARTITION BY dept_id ORDER BY salary) 看似能取最小,但有陷阱:当存在并列最小值时,它只返回排序后第一个出现的行,不保证是“任意一个最小值”,更不保证稳定(依赖物理顺序或主键隐式排序)。
- 真正要拿最小值,就该用
MIN(salary) OVER (PARTITION BY dept_id)—— 结果确定、无歧义 -
FIRST_VALUE适合取“某排序规则下的首条记录的完整字段”,比如“每个部门薪资最低的员工姓名”,这时需配合ORDER BY salary, emp_id避免不确定性 - MySQL 8.0+ 和 PostgreSQL 支持
MIN() OVER,但旧版 MySQL(
性能差异:窗口函数 vs 相关子查询
在千万级数据上,MIN() OVER (PARTITION BY ...) 通常比 (SELECT MIN(salary) FROM t2 WHERE t2.dept_id = t1.dept_id) 快 3–10 倍,因为前者只需一次扫描 + 排序/哈希分组,后者可能对每行都触发一次索引查找。
- 确保
PARTITION BY字段上有索引(如(dept_id, salary)联合索引对MIN(salary) OVER (PARTITION BY dept_id)有加速作用) - PostgreSQL 中若分区字段基数极高(比如每组只有 1–2 行),窗口函数优势减弱,甚至略慢于索引子查询
- SQL Server 的执行计划里注意看是否出现
Window Spool算子;若内存不足,它会落盘,拖慢响应
NULL 值怎么处理?MIN() 和 MAX() 窗口函数默认忽略 NULL
这和标准聚合函数行为一致:只要分组内至少有一个非 NULL 值,结果就不为 NULL;全为 NULL 才返回 NULL。如果你需要把 NULL 当作“最小”来参与比较(例如日志表中 end_time IS NULL 表示进行中,想把它排最前),就不能依赖 MIN()。
- 方案一:用
CASE WHEN end_time IS NULL THEN '1970-01-01' ELSE end_time END转换后再算MIN() - 方案二:改用
ARRAY_AGG(... ORDER BY ...)[1](PostgreSQL)或STRING_AGG(... ORDER BY ... LIMIT 1)(某些场景)绕过 NULL 过滤逻辑 - 别试图用
COALESCE(MIN(x), ...)来兜底——它掩盖了“本组无有效值”的事实,后续逻辑可能出错
MIN() OVER (PARTITION BY ...) 就是又准又快的答案。











