avg() over(rows between)是最直接的移动平均写法,需严格配合order by确保物理行序稳定,如rows between 2 preceding and current row计算含当前行共3行的均值,且必须避免排序列重复或缺失导致结果错位。

AVG() OVER(ROWS BETWEEN) 是最直接的移动平均写法
SQL 标准里没有“移动平均”这个函数,但用 AVG() 配合窗口函数的 ROWS BETWEEN 子句就能精准控制滑动窗口范围。关键不是套公式,而是理解 ROWS BETWEEN 的偏移逻辑:它按当前行在分区内的物理位置(不是时间或ID值)向前/向后数行。
常见错误是误以为 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 表示“最近3天”,其实它只认行序——如果某天数据缺失,就会漏掉那天,但窗口长度不变。
-
ROWS BETWEEN n PRECEDING AND CURRENT ROW:含当前行的前 n+1 行(比如 n=2 → 3行) -
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:当前行 + 前1行 + 后1行(共3行) - 必须搭配
ORDER BY(否则窗口无序,结果不可控) - 若需按时间对齐(如强制包含最近7天),得先用
GENERATE_SERIES或左连接补全日期,再算窗口
ORDER BY 必须用确定性排序字段,否则结果会漂移
窗口函数依赖 ORDER BY 定义行序,如果排序字段有重复值(比如多个订单同秒下单),数据库可能每次返回不同物理顺序,导致同一行的移动平均值变化。这不是 bug,是未定义行为。
解决方法很简单:在 ORDER BY 末尾追加一个唯一字段兜底,比如主键或时间戳+ID组合。
- 错:
ORDER BY order_time(order_time 重复时窗口不稳定) - 对:
ORDER BY order_time, order_id(保证每行位置唯一) - 对:
ORDER BY order_time, created_at, id(多级保底,适合日志类场景)
性能敏感场景要警惕窗口大小和分区粒度
ROWS BETWEEN 看似轻量,但窗口越大、分区越粗(比如不分区直接全表排序),计算成本越高。PostgreSQL 和 MySQL 8.0+ 能较好优化,但 SQLite 或旧版 SQL Server 可能全扫描。
- 分区(
PARTITION BY)能大幅降低单次计算量,比如按product_id分区算各商品的7日均值,比全表算快得多 - 避免
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(累积平均)用于大表,它等价于每行都重算前面所有行 - 如果只需近似值,可改用子查询 +
LIMIT模拟(不精确但快),或预聚合到每日快照表
NULL 值会让 AVG 自动跳过,但行计数仍受 ROWS 影响
AVG() 窗口函数默认忽略 NULL,这是好事;但要注意:即使某行 sales_amount 是 NULL,只要它落在 ROWS BETWEEN 范围内,就占用一个“槽位”,可能导致实际参与计算的非空值少于预期。
比如 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING 固定跨3行,但其中2行是 NULL,则 AVG() 只对1个非空值求均值(结果等于该值),而非报错或补零。
- 想把 NULL 当 0 处理?用
AVG(COALESCE(sales_amount, 0)) - 想排除 NULL 行再定窗口大小?得先用 CTE 过滤,再开窗(此时行序已变)
- 想保持窗口内行数恒定且强制填充?需要结合
LAG()/LEAD()手动取值,无法靠ROWS直接实现











