mysql 8.0+ 等支持窗口函数的数据库可用 avg() over(order by date_col rows between 6 preceding and current row) 计算7日移动平均,需按日期升序排序,缺数据时按行数而非日历天数滑动;sqlite等旧版需用自连接或子查询模拟,性能较差。

MySQL 8.0+ 用 AVG() OVER() 最直接
窗口函数是计算移动平均最自然的方式,前提是数据库版本支持。MySQL 8.0、PostgreSQL、SQL Server 2012+、Oracle 12c+ 都可用。
关键点在于正确设置 ROWS BETWEEN 6 PRECEDING AND CURRENT ROW:它表示“包含当前行和往前6行”,共7行——对应最近7天(假设数据按日期严格排序且每天一条)。
- 必须先按时间升序排序,否则
PRECEDING无意义:ORDER BY date_col - 如果某天缺数据,窗口仍按行数滑动,不是按日历天数补全;要严格按日历补全需先生成日期序列再
LEFT JOIN - 首6行结果为
NULL或部分平均(取决于是否加ROWS UNBOUNDED PRECEDING),通常可接受
SELECT date_col, value, ROUND(AVG(value) OVER (ORDER BY date_col ROWS BETWEEN 6 PRECEDING AND CURRENT ROW), 2) AS ma7 FROM sales;
SQLite 和旧版 MySQL 需用自连接或子查询
这些引擎不支持标准窗口函数,得靠 JOIN 或相关子查询模拟滑动窗口。性能较差,尤其数据量大时。
核心思路:对每条记录,找出它及之前6天内的所有记录,再算平均。注意日期比较必须用 BETWEEN 或 >=/,不能只靠行号。
- SQLite 中用
date(date_col, '-6 days')计算起始日期,配合BETWEEN - MySQL 5.7 可用
DATE_SUB(date_col, INTERVAL 6 DAY) - 务必给
date_col加索引,否则每次子查询都要全表扫描 - 若存在多条同一天数据,需先按天聚合(如
SUM()或AVG()),否则会重复计入
SELECT t1.date_col, t1.value, (SELECT AVG(t2.value) FROM sales t2 WHERE t2.date_col BETWEEN DATE_SUB(t1.date_col, INTERVAL 6 DAY) AND t1.date_col) AS ma7 FROM sales t1;
日期缺失时移动平均会“跳变”,必须预处理
真实业务中,周末、节假日常无销售记录。此时直接按行数或简单 BETWEEN 会导致窗口实际覆盖天数不足7天,比如周五的 ma7 可能只含周一到周五5天数据。
解决办法不是改窗口逻辑,而是先补全日期维度:
- 生成连续7天(或更长)的日期序列(用递归 CTE、临时表或应用层生成)
- 用
LEFT JOIN补上缺失日期,value设为0或NULL(根据业务含义选) - 再对补全后的结果跑窗口函数或子查询
- 注意:补
NULL会影响AVG()结果(AVG自动忽略NULL),补0则拉低均值——选哪个取决于“无数据”代表“零销量”还是“不可用”
PostgreSQL 里 generate_series() 是补日期的利器
比起手写递归或外部生成,PostgreSQL 提供了简洁方案:generate_series() 直接产出日期序列,配合 LEFT JOIN 十分干净。
它能避免手动构造日期表的麻烦,也比子查询更易读和维护。
- 用法示例:
SELECT generate_series('2024-01-01'::date, '2024-01-31'::date, '1 day'::interval)::date AS date - 与原始表
LEFT JOIN后,COALESCE(sales.value, 0)填补空值 - 之后再套一层窗口函数,逻辑就和 MySQL 8.0 完全一致
- 注意时区:
generate_series()返回timestamp,转date时可能受timezone设置影响
SELECT
d.date,
COALESCE(s.value, 0) AS value,
ROUND(AVG(COALESCE(s.value, 0)) OVER (
ORDER BY d.date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
), 2) AS ma7
FROM generate_series('2024-01-01'::date, '2024-01-31'::date, '1 day') AS d(date)
LEFT JOIN sales s ON d.date = s.date;
日期连续性、空值语义、数据库版本能力——这三者没对齐,算出来的“7天平均”就只是个数字,不是业务想要的趋势指标。











