应使用RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW(PostgreSQL/Snowflake),因ROWS按行数固定滑动易受缺日影响,而RANGE按时间值区间计算才真正表达“过去7天”语义。

窗口函数里怎么写“过去7天”的时间范围
窗口函数本身不直接支持“过去7天”这种动态日期偏移,ROWS BETWEEN 6 PRECEDING AND CURRENT ROW 看似能凑够7行,但前提是数据每天一条且无缺失。一旦某天没记录(比如周末无交易),这7行就可能跨过10天甚至更久,结果完全失真。
真正可靠的做法是用 RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW(PostgreSQL / Snowflake 支持),或在 MySQL 8.0+ 中改用 RANGE BETWEEN 6 PRECEDING AND CURRENT ROW 配合 ORDER BY date —— 但注意:MySQL 的 RANGE 只支持数字类型,不能直接用于日期,必须先转换为天数差。
常见错误现象:AVG(price) OVER (ORDER BY trade_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) 在数据稀疏时输出明显偏低的平均值,因为窗口实际覆盖了远超7天的时间跨度。
不同数据库对日期窗口的支持差异
- PostgreSQL:支持
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW,最直观,但要求 ORDER BY 列是 DATE 或 TIMESTAMP 类型
- Snowflake:同样支持
INTERVAL 形式的 RANGE,语法一致
- BigQuery:不支持
RANGE + INTERVAL,必须用自连接或 UNNEST(GENERATE_DATE_ARRAY(...)) 配合 LEFT JOIN 模拟
- MySQL 8.0+:
RANGE 仅接受数值,需先计算 DATEDIFF(trade_date, '1970-01-01') 转成整数再窗口
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW,最直观,但要求 ORDER BY 列是 DATE 或 TIMESTAMP 类型 INTERVAL 形式的 RANGE,语法一致 RANGE + INTERVAL,必须用自连接或 UNNEST(GENERATE_DATE_ARRAY(...)) 配合 LEFT JOIN 模拟 RANGE 仅接受数值,需先计算 DATEDIFF(trade_date, '1970-01-01') 转成整数再窗口 性能影响:用 RANGE + INTERVAL 通常比 ROWS 开销大,尤其当分区大、日期分布不均时,引擎要逐行定位时间边界。
如何处理缺失日期导致的移动平均断层
即使窗口定义正确,原始表若缺日期(例如没有2024-04-05的数据),AVG() 就不会把那天当成 0 或 NULL 补进去 —— 它只是跳过,导致平均值基于更少的有效天数计算。
解决路径只有两条:
- 预先补全日期:用递归 CTE 或日历表
LEFT JOIN原始数据,把缺失日的指标设为NULL或0(根据业务含义选) - 后续过滤:在外部加
HAVING COUNT(*) = 7来只保留满7天的行(但会丢数据)
示例(PostgreSQL 补日期):
WITH date_series AS (
SELECT generate_series(
MIN(trade_date),
MAX(trade_date),
'1 day'::interval
)::date AS dt
FROM trades
),
filled AS (
SELECT ds.dt, COALESCE(t.value, NULL) AS value
FROM date_series ds
LEFT JOIN trades t ON ds.dt = t.trade_date
)
SELECT dt,
AVG(value) OVER (
ORDER BY dt
RANGE BETWEEN '6 days'::interval PRECEDING AND CURRENT ROW
) AS ma7
FROM filled;
为什么不能直接用 GROUP BY + DATE_SUB() 模拟
有人尝试写 GROUP BY DATE_SUB(trade_date, INTERVAL (WEEKDAY(trade_date)) DAY) 这类逻辑来“分组后算平均”,但这算的是按周截断的静态分组平均,不是逐日滚动的移动平均。它会让 4月1日~4月7日所有数据挤进同一个桶,而移动平均要求 4月1日只看4月1日,4月2日看4月1-2日,4月3日看4月1-3日……以此类推。
本质区别在于:移动平均必须是每行一个独立窗口,而不是聚合后生成新行。用 GROUP BY 会丢失原始粒度,也没法做到“每日更新一个新值”。
真正难的不是写对语法,而是确认你的日期列是否连续、是否需要插值、以及目标系统到底支不支持语义准确的 RANGE 时间窗口 —— 这些不提前理清,跑出来的 ma7 数字看着整齐,其实已经偏离业务本意了。










