AVG() OVER 不能直接套用日期减法,因为 ROWS BETWEEN 按物理行数滑动、RANGE BETWEEN 按值范围聚合,均无法保证覆盖连续自然日;必须先补全缺失日期(如用递归CTE或GENERATE_SERIES生成日历表),再LEFT JOIN原始数据,才能实现严格7日日历窗口。

AVG() OVER 为什么不能直接套用日期减法?
很多人写 AVG(price) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) 后发现结果不对——不是按「最近7个自然日」算,而是按「最近7条记录」算。只要某天没数据(比如周末无交易),移动窗口就会跳过那天,导致平均值覆盖的实际天数少于7天。
如何确保严格按日期范围取7天?
必须把缺失日期补全,否则 ROWS 或 RANGE 都无法按日历对齐。常见做法是先用递归 CTE 或日历表生成连续日期,再 LEFT JOIN 原始数据。PostgreSQL/SQL Server 可用 GENERATE_SERIES 或 DATEADD;MySQL 8.0+ 推荐用递归 CTE 构造日期序列:
WITH date_series AS (
SELECT MIN(date) AS d FROM trades
UNION ALL
SELECT d + INTERVAL 1 DAY FROM date_series
WHERE d < (SELECT MAX(date) FROM trades)
),
filled AS (
SELECT ds.d AS date, COALESCE(t.price, NULL) AS price
FROM date_series ds
LEFT JOIN trades t ON ds.d = t.date
)
SELECT date,
AVG(price) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7
FROM filled
ORDER BY date;
MySQL 5.7 或无递归支持时怎么处理?
只能靠外部补全或应用层兜底。硬要在 SQL 里做,得预先建一张最小粒度为日的 calendar 表(含从最早到最晚所有日期),然后走 LEFT JOIN。否则:AVG() OVER 在缺失日期处会断档,且 RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW 仅在 MySQL 8.0.2+ 支持,且要求 date 是 DATE 类型(不能是 DATETIME 含时分秒)。
- 确认字段类型:用
CAST(date AS DATE)强制截断时间部分 - 检查排序稳定性:如果同一天有多条记录,
ORDER BY date不足以保证顺序,需加id或timestamp辅助 - NULL 处理:
AVG()自动忽略 NULL,但若整段窗口全是 NULL(如连续7天无数据),结果为 NULL —— 这是正确行为,不是 bug
性能和边界情况要注意什么?
补全日期会让中间结果集显著膨胀,尤其跨年查询时。别在大表上直接跑递归 CTE;优先考虑物化日历表或用应用代码预计算。另外注意:窗口函数在 WHERE 之后执行,所以过滤掉的日期不会进入窗口计算——想算「全量7日均值」就得把过滤逻辑移到子查询或 CTE 中。
最常被忽略的一点:移动平均线本质是滞后指标,CURRENT ROW 对应的是「截至当天」的均值,不是「当天发生」的均值。如果你要的是盘中实时更新,这个延迟无法绕过。

















