sql中无内置moving_average()函数,需用avg() over()实现,但各引擎对rows/range、时间定义、空值处理及排序唯一性支持差异大,且存在性能与语义偏差风险。

SQL里没有内置的MOVING_AVERAGE()函数,得靠窗口函数手动实现
几乎所有主流SQL引擎(PostgreSQL、SQL Server、MySQL 8.0+、BigQuery、Snowflake)都支持AVG() OVER(),但行为细节差异很大。核心不是“有没有”,而是“怎么定义‘移动’的范围”——是按时间顺序?按行数?是否允许空值穿透?
- 用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW是按物理行数算,适合已排序且无时间断点的数据 - 用
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW(PostgreSQL/BigQuery)或RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(需配合ORDER BY date)才能真正按业务时间滚动 - MySQL 8.0不支持
RANGE带INTERVAL,只能先用DATE_SUB()生成辅助日期列再关联
时间序列有缺失日期时,ROWS会算错,必须补全日期再计算
比如销售表每天只记有订单的日子,周一到周五有数据、周末为空。用ROWS BETWEEN 4 PRECEDING AND CURRENT ROW算5日均值,遇到周一就会把上周五、四、三、二和周一全拉进来——实际跨了7个自然日,但只算了5天数据,趋势会被平滑失真。
- 先用递归CTE或日期生成表(如
GENERATE_DATE_ARRAY()in BigQuery)补全所有目标日期 - 再
LEFT JOIN业务表,用COALESCE(sales, 0)填充空值(注意:填0还是NULL取决于业务含义) - 最后在完整日期序列上跑
AVG() OVER (ORDER BY date ROWS BETWEEN 4 PRECEDING AND CURRENT ROW)
ORDER BY字段必须唯一,否则RANGE窗口可能吞掉多行
如果按order_date排序,但一天有多笔订单,RANGE BETWEEN INTERVAL '30 days' PRECEDING AND CURRENT ROW会把同一天所有行视为“同一位置”,导致窗口内行数爆炸。错误现象:某天移动均值突然飙升,远超合理范围。
- 强制让排序键唯一:
ORDER BY order_date, order_id(推荐) - 或改用
ROWS并明确指定数量,避开RANGE的重复键问题 - 检查执行计划里的
window function节点是否出现frame size异常增长
性能陷阱:大表上AVG() OVER()可能比预期慢很多
表面看只是加个窗口函数,但底层要维护滑动窗口状态。当分区大(如全表不分区)、排序字段无索引、或窗口跨度过大(如90 DAYS)时,内存占用和CPU消耗会陡增。
- 给
ORDER BY字段建索引(如CREATE INDEX idx_date ON sales(order_date))能显著加速 - 避免在子查询里嵌套多层窗口函数,先物化中间结果(如用
CREATE TEMP TABLE或CTE) - 在Snowflake/BigQuery中,确认
WAREHOUSE_SIZE或RESERVATION资源足够,否则窗口计算会被限速
移动平均本身逻辑简单,但真实业务数据的稀疏性、时间语义歧义、引擎实现差异,会让结果和预期差很远。动手前先用100行样本验证窗口边界是否符合业务定义——比如“过去7天”到底包不包括当天,“工作日均值”要不要跳过节假日。











