必须用窗口函数实现7天滑动平均:ROWS BETWEEN 6 PRECEDING AND CURRENT ROW要求日期严格升序且无缺失,否则需预补全;日期不连续时应改用RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW(仅PostgreSQL/BigQuery支持);MySQL 8.0+/SQL Server等需适配方案,旧版MySQL依赖带索引的子查询。

SQL里用AVG()配合ROWS BETWEEN 6 PRECEDING AND CURRENT ROW实现7天滑动平均
直接结论:必须用窗口函数,不能靠子查询或自连接硬凑——后者在数据量大时性能崩盘,且难以处理日期不连续的情况。
核心逻辑是把“过去7天”映射成“当前行及往前6行”,前提是数据已按日期严格升序排列。如果原始表里date有重复或缺失,得先用GENERATE_SERIES(PostgreSQL)或日期补全逻辑预处理,否则ROWS会数错“天数”。
- MySQL 8.0+、PostgreSQL、SQL Server、BigQuery 都支持该语法;SQLite 和旧版 MySQL 不支持窗口函数,需换方案
-
ORDER BY date必须显式写出,否则ROWS行为未定义 - 若某天无数据,该行不会被跳过,而是让窗口“悬空”——比如第2天缺失,则第3天的窗口只含第1、3天两行,不是你想要的“物理7天”
当日期不连续时,改用RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW
这是真正按时间跨度算“过去7天”的写法,但仅 PostgreSQL 和 BigQuery 原生支持RANGE + INTERVAL。MySQL 和 SQL Server 还不认这种组合,强行写会报错ERROR 3589 (HY000): Window '<window_name>' with RANGE frame requires ORDER BY to be of a single column of numeric or temporal type</window_name>之类。
- PostgreSQL 示例:
AVG(sales) OVER (ORDER BY order_date RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW)
- BigQuery 用
DATE_SUB(CURRENT_DATE(), INTERVAL 6 DAY)做动态边界,但必须配合UNBOUNDED PRECEDING和过滤逻辑,实际更啰嗦 - SQL Server 要用
LAG()手动展开7列再AVG(),可读性差且无法应对动态天数需求
MySQL 5.7 或不支持窗口函数的老版本只能用关联子查询
虽然慢,但能跑通。关键点在于子查询里用WHERE date >= DATE_SUB(t1.date, INTERVAL 6 DAY)限定时间范围,而不是依赖行号。
- 必须给
date字段加索引,否则全表扫描一次就卡死 - 如果存在多条同一天记录,子查询会重复计算,结果偏高——需先按天聚合再套窗口
- 示例片段:
SELECT t1.date, (SELECT AVG(t2.sales) FROM sales t2 WHERE t2.date BETWEEN DATE_SUB(t1.date, INTERVAL 6 DAY) AND t1.date) AS moving_avg FROM sales t1
容易被忽略的NULL值和边界行为
窗口函数默认跳过NULL值计算,但首尾几天窗口不满7行时,结果仍是有效均值(比如第1天只有1个数,就返回那1个数)。这通常符合预期,但若业务要求“不足7天不输出”,就得加CASE WHEN COUNT(*) OVER (...) 判断。
- 所有数据库对
ROWS BETWEEN开头几行的处理一致:第1行窗口=第1行,第2行=第1–2行,依此类推 - PostgreSQL 中
RANGE模式下,若当天多条记录,会全部纳入同一窗口;ROWS模式则按物理顺序取固定行数,可能漏掉同天其他记录 - 别在
WHERE里提前过滤掉sales IS NOT NULL——否则窗口内行数变少,7天逻辑就歪了
ROWS硬对齐还是用RANGE保语义,别一上来就抄示例。

















