窗口函数让“既要聚合、又要明细”成为可能,通过OVER()子句在不丢失原始行的前提下实现分组聚合与排序计算,支持LAG/LEAD环比、ROWS BETWEEN滚动计算等高级分析,是高级SQL能力的分水岭。

窗口函数让“既要聚合、又要明细”变成一句废话
传统 GROUP BY 一执行,原始行就没了——想看每个员工的工资,又想看他们部门平均工资?得写子查询或自连接。窗口函数直接在 SELECT 里加个 AVG(salary) OVER (PARTITION BY dept),每行都带着自己部门的均值回来,不丢数据、不增行数。
- 常见错误:把
OVER()漏写或写成空括号,结果变成全表窗口(比如全公司平均),而不是按部门算 - ORDER BY 在窗口里不是可选的:用
ROW_NUMBER()或计算累计和时,没ORDER BY会报错或返回不可靠结果 - MySQL 8.0+、PostgreSQL、SQL Server 2012+、Oracle、Doris、StarRocks 都支持;但 SQLite(除非是最新 dev 版)和旧版 Hive 不支持
LAG() 和 LEAD() 是环比/同比的底层开关
做月度销售环比,不用再拼日期逻辑 + 左连接上个月表。一行 LAG(amount) OVER (PARTITION BY region ORDER BY sale_month) 就能拿到上月值,再跟当前行做减法除法就行。
- 容易踩坑:如果
sale_month有缺失(比如某地区某月没数据),LAG()会跳过空值取更早的,导致“上月”错位——建议先用GENERATE_SERIES或补全月份再开窗 -
LAG(col, 2)表示取前两行,不是“上个月”,顺序完全依赖ORDER BY的列和方向 - 性能上比自连接快 2–3 倍(实测百万级订单表,窗口函数耗时约 800ms,等效自连接 2.5s)
ROWS BETWEEN 控制移动窗口,不是玄学
算 7 日滚动销售额?不是靠应用层循环,而是数据库原生支持:SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)。这里 ROWS 看的是物理行序,RANGE 看的是值范围(比如 “过去 7 天” 要用 RANGE INTERVAL '6 days' PRECEDING,但仅 PostgreSQL/Oracle 支持)。
- MySQL 不支持
RANGE+INTERVAL,只能用ROWS+ 排序后硬凑天数,遇到重复日期可能不准 -
UNBOUNDED PRECEDING是安全写法,但别滥用——没PARTITION BY时,它会让窗口跨全表,大表易 OOM - 排序字段必须有索引,否则
ORDER BY+ 窗口框架会触发 filesort,拖慢几倍
为什么它是高级 SQL 的分水岭?
因为能否用好窗口函数,直接暴露你是在写“能跑就行”的 SQL,还是在构建可维护、可复用、可下推的数据逻辑。它不解决“查不到”的问题,而是解决“查出来没法直接用”的问题——比如运营要一份带排名、带累计、带环比、还保留用户 ID 的明细报表,不用窗口函数就得四张临时表嵌套三层子查询。而一个写对的 OVER() 子句,就是把这四步压进一行表达式里。
真正卡住人的从来不是语法,而是想清楚:我要的“窗口”,到底该按什么分(PARTITION BY)、按什么排(ORDER BY)、框多大(ROWS/RANGE)。这三个条件少一个,结果就偏一点;错一个,整张报表就废掉。










