窗口函数不能直接嵌套聚合函数,因二者语义冲突:聚合压缩行数,窗口函数作用于现有行集;须先用group by或cte计算分组结果,再在其上开窗。

窗口函数嵌套聚合为什么报错 GROUP BY 冲突?
直接在 SELECT 里写 MAX(SUM(x)) OVER (PARTITION BY y) 会报错,因为 SUM() 是聚合函数,而窗口函数不能直接包裹未分组的聚合函数——SQL 引擎无法确定内层聚合的粒度。必须先用子查询或 CTE 把分组聚合结果算出来,再在其上开窗。
典型错误现象:ERROR: aggregate function calls cannot contain window function calls(PostgreSQL)或类似提示(MySQL 8.0+、SQL Server 同理)。
- 正确路径:先
GROUP BY得到每组一个值(比如每组销售额总和),再把这个结果集作为输入,用窗口函数处理 - 别试图“一步到位”,窗口函数只作用于当前行所在的结果集,它不改变行数,也不执行分组;聚合函数则压缩行数——二者语义冲突
- CTE 最清晰:
WITH group_sum AS ( SELECT region, SUM(sales) AS total_sales FROM orders GROUP BY region ) SELECT region, total_sales, RANK() OVER (ORDER BY total_sales DESC) AS rank_by_sales FROM group_sum;
ROW_NUMBER() 和 RANK() 在分组内排序时差在哪?
当你要“取每组销量前三的门店”,用错序号函数会导致重复名次漏数据或挤占名额。关键看业务是否允许并列。
-
ROW_NUMBER()严格编号:即使sales相同,也按某种隐式顺序(如物理存储顺序)强行排 1/2/3,不会跳过数字 -
RANK()并列跳号:两个第 1 名后,下一个是第 3 名(跳过 2) -
DENSE_RANK()并列不跳号:两个第 1 名后,下一个是第 2 名 - 实战建议:取 Top-N 且需去重时优先用
ROW_NUMBER();做绩效排名展示时用RANK()更符合管理习惯
如何用 LAG()/LEAD() 实现分组内环比计算?
环比不是简单减上一行,必须确保 LAG() 拿到的是**同一组内、时间上紧邻的前一条记录**,否则跨组污染结果。
- 务必在
OVER子句中同时指定PARTITION BY和ORDER BY:LAG(sales, 1) OVER (PARTITION BY region ORDER BY month)
- 如果
ORDER BY缺失,LAG()行为不可预测(多数引擎按无序处理,结果随机) - 注意空值:首行没有前值,
LAG()返回NULL,需用COALESCE()或CASE处理,例如:sales - COALESCE(LAG(sales) OVER (PARTITION BY region ORDER BY month), 0)
- MySQL 8.0+ 和 PostgreSQL 支持负偏移(
LEAD(x, -1)),但可读性差,不推荐
多个窗口函数共用 PARTITION BY 会重复计算吗?
不会。现代 SQL 引擎(PostgreSQL、SQL Server、Oracle、MySQL 8.0+)会对相同 PARTITION BY ... ORDER BY 的窗口函数自动复用排序结果,避免多次排序开销。
- 但若两个函数的
ORDER BY不一致(比如一个按date,一个按amount),引擎会分别排序,性能下降明显 - 极端情况:5 个窗口函数,各自
ORDER BY都不同 → 可能触发 5 次全量排序,比单次GROUP BY还慢 - 优化技巧:提前用
ORDER BY排好序的 CTE,再在其中调用多个窗口函数,强制共享排序上下文(部分引擎如 PostgreSQL 会自动优化,但显式写出更可控)
分组内自定义聚合真正的复杂点不在语法,而在于想清楚“聚合发生在哪一层”——是先压缩成组级数据再加工,还是保留明细行靠窗口函数逐行推演。很多人卡在第一步就选错了方向。










