avg() over 必须显式指定 rows between 才能实现滑动窗口,仅 order by ts 默认按 range 计算累积平均;需搭配唯一排序(如 ts, id)和 partition by 防跨组污染,且首行窗口自动截断属正常行为。

AVG() OVER 必须带 ROWS BETWEEN 才算滑动窗口
只写 AVG(price) OVER (ORDER BY ts) 不会得到滑动平均,PostgreSQL 默认按 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 计算——也就是“值相同就全拉进来”,一旦 ts 有重复(比如秒级时间戳下多笔订单),结果就不可控。真正实现按行数滑动的唯一方式是显式写出 ROWS BETWEEN。
常见错误写法:AVG(price) OVER (ORDER BY ts ROWS BETWEEN 2 PRECEDING AND CURRENT ROW —— 少了个右括号,直接报错;或者漏掉 ROWS BETWEEN,结果变成累积平均。
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW:含当前行共 3 行(前 2 + 当前) -
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:中心对称 3 行(前 1 + 当前 + 后 1) -
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW:7 日移动平均(含当天)
ORDER BY 字段必须唯一,否则窗口会漂移
用 ORDER BY order_date 看似合理,但若多条记录同属一天,数据库不保证它们之间的物理顺序。同一查询执行两次,ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 可能覆盖不同行,导致指标忽高忽低。
解决方法是补一个确定性次序字段:
- 优先用
ORDER BY order_time, order_id(假设order_id是主键或唯一递增) - 如果时间字段只有日期精度,先生成稳定序号:
ROW_NUMBER() OVER (ORDER BY order_date, id),再按该序号开窗 - 千万别用
ORDER BY random()或无索引字段,性能差且结果不可复现
PARTITION BY 漏写会导致跨组污染
计算每个用户的 7 日销售额均值时,如果只写 AVG(sales) OVER (ORDER BY ts ROWS BETWEEN 6 PRECEDING AND CURRENT ROW),PostgreSQL 会把所有用户混在一起排序——A 用户第 1 笔订单可能和 B 用户最近 6 笔一起被框进窗口,结果完全失真。
必须显式分组:
- 按用户隔离:
AVG(sales) OVER (PARTITION BY user_id ORDER BY ts ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) - 按设备类型+日期双重分组:
PARTITION BY device_type, DATE(ts)(注意:DATE(ts) 本身不唯一,仍需加二级排序) - 分区字段没索引?查询可能变慢,但比结果错乱好
边界行自动截断是正常行为,不是 bug
用 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 算第 1 行时,窗口实际只包含 1 行;第 2 行最多 2 行;从第 3 行起才稳定为 3 行。这是 SQL 标准定义,所有主流数据库(PostgreSQL、SQL Server、MySQL 8.0+)都如此。
如果你需要“强制满窗”(比如少于 3 行就返回 NULL),得手动判断:
AVG(price) FILTER (WHERE row_num >= 3) OVER ( ORDER BY ts ROWS BETWEEN 2 PRECEDING AND CURRENT ROW )
但更常见也更安全的做法是接受默认截断——它反映真实数据覆盖度,强行补 NULL 反而掩盖了冷启动问题。
最容易被忽略的是:NULL 值会被 AVG() 自动跳过,但依然占窗口“槽位”。比如窗口含 3 行,其中 2 行 price 是 NULL,AVG() 只基于剩下那个非 NULL 值计算,结果等于该值本身。这不是缺陷,而是设计使然——你需要的到底是“有效样本均值”,还是“窗口内所有值(含 NULL 占位)的均值”,得提前想清楚。










