窗口函数比子查询更高效,avg()和sum()配合over()可直接计算移动平均与累计销量,避免性能差、难维护的嵌套子查询。

用窗口函数替代子查询更高效
直接写子查询算移动平均或累计销量,在大多数现代 SQL 引擎里既慢又难维护。真正该用的是 AVG() 和 SUM() 配合 OVER() 窗口子句——子查询只是历史兼容方案,现在没必要硬扛。
比如想算过去 7 天的移动平均销量,用子查询得对每行都嵌套一次 SELECT AVG(sales) FROM sales WHERE date BETWEEN ...,数据一过万行性能就明显下滑;而窗口函数一次性扫描就能出结果。
-
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW表示“包含当前行和前 6 行”,共 7 行窗口 - 必须按时间排序:
ORDER BY sale_date是ROWS窗口的前提,漏掉会报错或结果错乱 - 如果存在多笔同日期记录,建议先按
sale_date, id双排序,避免窗口边界漂移
累计销量:SUM() OVER(ORDER BY ...) 最简写法
累计销量本质就是“从第一行加到当前行”,SUM(sales) OVER (ORDER BY sale_date) 一行搞定。不需要自连接,也不需要关联子查询。
注意:ORDER BY 必须明确,否则累计逻辑无定义;如果业务要求按订单生成时间而非销售日期累计,就换成 ORDER BY created_at。
- 默认等价于
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - 若要排除当前行(即“截至昨日累计”),改用
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING - MySQL 8.0+、PostgreSQL、SQL Server 2012+、Oracle 10g+ 都支持,SQLite 3.25+ 也支持
移动平均值:AVG() + ROWS 指定滑动范围
移动平均的关键是控制窗口大小和方向。AVG(sales) OVER (ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) 就是算“含当天在内的最近 3 天平均销量”。
常见误操作是写成 RANGE(按值分组)而不是 ROWS(按行数),尤其当某天没数据时,RANGE 会跳过空缺日期导致窗口实际变大。
- 日期有空缺?先用
GENERATE_SERIES(PostgreSQL)或递归 CTE 补全日期维度,再左连接销量表 - 想按自然周/月滚动?先用
DATE_TRUNC('week', sale_date)或YEARWEEK()聚合到周期粒度,再套窗口函数 - SQL Server 中
ROWS不支持变量(如ROWS BETWEEN @n PRECEDING),需用动态 SQL 或改用OFFSET FETCH分页模拟
兼容旧版本 MySQL(5.7 及以下)的子查询写法
如果真被卡在 MySQL 5.7 且无法升级,只能用相关子查询,但必须加索引,否则 O(n²) 查询秒变卡死。
例如累计销量:
SELECT s1.sale_date,
(SELECT SUM(s2.sales)
FROM sales s2
WHERE s2.sale_date
- 必须在
sale_date上建索引,否则子查询每次全表扫描 - 移动平均需双层子查询,性能更差,例如过去 7 天平均要写
WHERE s2.sale_date BETWEEN DATE_SUB(s1.sale_date, INTERVAL 6 DAY) AND s1.sale_date - NULL 值会影响
SUM/AVG结果,建议在子查询里加WHERE s2.sales IS NOT NULL











