应使用MAX()/MIN()窗口函数配合OVER(PARTITION BY ...)直接计算组内极值,保留所有原始行;若需获取极值对应完整记录,则须用ROW_NUMBER()窗口函数排序取首行,而非仅依赖聚合函数。

用 MAX() 和 MIN() 窗口函数直接计算组内极值
窗口函数是解决“每组最值”问题最干净的方式,不用 GROUP BY 聚合再 JOIN 回原表。关键在于正确写 OVER(PARTITION BY ...) 子句。
常见错误是漏写 PARTITION BY,导致算出的是全表最大/最小值;或者误把 ORDER BY 当成分组依据(它只影响帧范围,不定义分组)。
-
MAX(price) OVER (PARTITION BY category):每个category内的最高价,保留所有原始行 - 若还需知道哪一行是最大值,可配合
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) - 注意:
NULL值默认被忽略,除非显式用NULLS FIRST或NULLS LAST(PostgreSQL/Oracle 支持,MySQL 8.0+ 不支持该语法)
MySQL 8.0+ 和 PostgreSQL 中的兼容写法差异
主流数据库都支持标准语法,但细节有坑。比如 MySQL 8.0 对 MIN()/MAX() 窗口函数无额外限制,而 PostgreSQL 允许在 OVER 中加 FRAME 子句(如 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),但对极值函数实际无效——因为 MAX 本就默认看整个分区。
- 安全写法统一用:
MAX(col) OVER (PARTITION BY group_col),不加ORDER BY或FRAME - SQL Server 2012+ 同样支持,但不支持
NULLS FIRST/LAST - SQLite 3.25+ 支持窗口函数,但
MIN/MAX行为与标准一致
当需要同时返回最大值行和最小值行时,别硬套一个窗口函数
如果目标不是“每行标出组内最大值”,而是“取出每组中 price 最高和最低的完整记录”,用两个 ROW_NUMBER() 更可靠:
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn_max,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price ASC) AS rn_min
FROM products
) t
WHERE rn_max = 1 OR rn_min = 1;这里容易踩的坑是用 RANK() ——遇到相同极值会跳号,导致漏行;而 ROW_NUMBER() 保证每行唯一编号,哪怕 price 相同也强制区分。
- 若允许并列(即多个相同最大值都需返回),改用
RANK(),但要确认业务是否接受重复 - 性能上,两次排序比一次
MAX/MIN窗口开销略大,但这是语义决定的,无法绕过
窗口函数本身不改变行数,所以“找出各组最大值与最小值”这个需求,首先要分清你到底要数值、要标记、还是要原始记录——选错方向,后面所有写法都会偏。

















