窗口函数不是group by替代品,而是对每行做上下文感知计算:保留原始行数,通过partition by分组、order by排序及rows/range定义帧,在每行附加基于窗口的统计值。

窗口函数不是GROUP BY的替代品,而是对每一行做“上下文感知计算”
很多人一看到 OVER() 就下意识类比 GROUP BY,结果发现数据行数没变、聚合值却“错位”了。这是因为窗口函数不折叠行,而是在保留原始行的基础上,为每行附加一个基于指定范围(窗口)算出的值。比如用 ROW_NUMBER() OVER (ORDER BY score DESC) 给所有学生按分数排连续名次,原始 100 行还是 100 行,每行多一个数字——这和 GROUP BY class 后只剩几个分组完全不同。
关键区别在于执行顺序:WHERE → GROUP BY → HAVING → WINDOW → ORDER BY → LIMIT。窗口函数在 GROUP BY 之后、ORDER BY 之前生效,所以它能看到分组后的聚合结果(如配合 SUM() OVER (PARTITION BY dept)),但不能直接“代替”分组聚合来减少行数。
必须写清楚 PARTITION BY 和 ORDER BY,否则默认是整张表+无序
省略 PARTITION BY 意味着把整张表当一个窗口;省略 ORDER BY 在多数聚合类窗口函数中会导致结果不可预测(比如 AVG(score) OVER (PARTITION BY class) 不加 ORDER BY,MySQL 8.0 会警告“frame is undefined”,且不同执行可能返回不同值)。实际使用中几乎总要显式声明这两项:
-
PARTITION BY定义“分组逻辑”,类似GROUP BY,但不删行 -
ORDER BY不仅决定排序,还隐式定义窗口帧(frame),影响ROWS BETWEEN的起点 - 如果只想要分区但不排序(比如单纯计数),也得写
ORDER BY id占位,否则 MySQL 可能报错或行为异常
示例:统计每个班级内学生按入学时间的累计人数:
SELECT name, class, enroll_date,<br> COUNT(*) OVER (PARTITION BY class ORDER BY enroll_date) AS cum_count<br>FROM student;
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING 这类帧子句容易写反
窗口帧(frame)控制“当前行参考哪几行”,默认对排名类函数(RANK(), ROW_NUMBER())无效,只对聚合类(SUM(), AVG(), MAX())起作用。最常踩的坑是把 UNBOUNDED PRECEDING 和 UNBOUNDED FOLLOWING 搞混:
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:从分区开头到当前行(常用作累计和) -
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING:从当前行到分区末尾(比如“剩余未处理订单数”) - 漏写
ROWS或写成RANGE可能导致性能骤降(RANGE需要值去重排序,且 MySQL 8.0 对RANGE支持有限)
错误示例:SUM(sales) OVER (PARTITION BY region ORDER BY month ROWS BETWEEN CURRENT ROW AND UNBOUNDED PRECEDING) —— UNBOUNDED PRECEDING 不能跟在 CURRENT ROW 后面,语法直接报错 ERROR 3586 (HY000): Window 'w' frame start cannot be after window frame end。
ORDER BY 中的 NULL 值会影响窗口帧边界,且默认排序行为因 SQL_MODE 而异
MySQL 8.0 默认把 NULL 排在最前面(ORDER BY x ASC 时),但如果你开了 SQL_MODE=HIGH_NOT_PRECEDENCE 或某些兼容模式,行为可能变化。更麻烦的是:当 ORDER BY 字段含 NULL,且你用了带 ROWS 的帧,MySQL 会把所有 NULL 行挤在一起并视为“同一位置”,导致帧计算跳过或重复包含它们。
- 安全做法:显式控制
NULL排序,比如ORDER BY score DESC NULLS LAST(MySQL 8.0.22+ 支持) - 旧版本可用
ORDER BY score IS NULL, score DESC替代 - 如果业务允许,提前用
COALESCE(score, 0)处理空值,避免帧逻辑被干扰
这个细节在做“前 N 名”或“滚动平均”时特别关键——一行 NULL 可能让整个窗口偏移一格,而错误往往只在特定数据分布下才暴露。











