窗口函数需用OVER()子句实现明细行保留与汇总计算,如SUM(amount) OVER()得全表总和,SUM(amount) OVER(PARTITION BY category)按类小计,加ORDER BY可实现累积或滚动计算,但须注意性能与NULL处理。

窗口函数怎么写才能既保留每行数据又算出汇总值
直接用 SUM() 这类聚合函数会强制分组或报错,窗口函数是唯一不破坏明细行的解法。关键在语法结构:必须带 OVER() 子句,且里面不能写 GROUP BY —— 否则就退化成普通聚合了。
常见错误现象:SELECT id, name, SUM(amount) FROM orders; 报错 “column 'id' must appear in the GROUP BY clause”,或者返回单行结果,明细全丢。
-
SUM(amount) OVER():全表总和,每行都显示同一数值 -
SUM(amount) OVER(PARTITION BY category):按分类算小计,但每行仍保留原始明细 - 没写
OVER()就是普通聚合,SQL 引擎立刻拒绝执行
ORDER BY 在 OVER() 里加不加,结果差很多
加 ORDER BY 会让窗口函数变成“累积计算”,比如累计求和、移动平均;不加就是静态分组/全表汇总。很多人忽略这点,导致数值对不上预期。
使用场景:想看“到当前行为止的销售总额”,就得写 SUM(amount) OVER(ORDER BY created_at);如果只是要每个用户的总消费额,就用 SUM(amount) OVER(PARTITION BY user_id)。
- 加
ORDER BY后,默认等价于RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - 显式写
ROWS BETWEEN ...可控性更强,比如做 7 天滚动求和:SUM(amount) OVER(ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) - MySQL 8.0+、PostgreSQL、SQL Server 都支持;SQLite 3.25+ 支持,但旧版不认
OVER
NULL 值和空分区会让窗口函数悄悄出错
窗口函数对 NULL 默认跳过(SUM、AVG 等),但 COUNT(*) 会算进去,COUNT(col) 不算 NULL —— 这点和普通聚合一致,但容易在窗口上下文中被忽略。
更隐蔽的问题是 PARTITION BY 字段为 NULL 时,所有 NULL 值会被归进同一个隐式分区。比如 PARTITION BY region,所有 region 是 NULL 的记录会一起算一个汇总值,而不是各自独立。
- 检查数据质量:先
SELECT COUNT(*) FROM t WHERE region IS NULL;看有没有意外空值 - 需要排除空值参与分区:改用
PARTITION BY COALESCE(region, 'unknown') -
ROW_NUMBER() OVER(...)遇到相同排序值会强行编号,而RANK()和DENSE_RANK()行为不同,别默认用ROW_NUMBER
性能陷阱:窗口函数不是免费的,大表慎用未优化的 ORDER BY
带 ORDER BY 的窗口函数通常触发全局排序,数据量一过百万,响应时间可能从毫秒级跳到秒级。没有索引支撑的排序字段,问题更明显。
可观察执行计划里的 WindowAgg 或 Sort 节点耗时占比。实际项目中,90% 的误用都源于没意识到这个开销。
- 确保
OVER(ORDER BY x)中的x列有索引(尤其是时间戳、ID 类字段) - 避免在
SELECT中混用多个带不同ORDER BY的窗口函数,比如同时写SUM() OVER(ORDER BY a)和AVG() OVER(ORDER BY b),会触发两次排序 - 如果只要全表汇总值,用子查询或 CTE 预先算好,比窗口函数更轻量:
(SELECT SUM(amount) FROM orders)
PARTITION BY 写错,或漏掉 ORDER BY 的边界定义,结果就可能完全偏离预期。

















