rows between边界必须成对出现,漏写and会报错;order by为强制前提,需加唯一二级排序防漂移;时间滚动均值须先补日期再开窗;分组移动平均必须显式指定partition by。

ROWS BETWEEN 的边界必须成对出现,漏写 AND 就报错
PostgreSQL 要求 ROWS BETWEEN 后面必须明确写出起始和结束边界,缺一不可。常见错误是只写 ROWS BETWEEN 2 PRECEDING,结果直接报错:ERROR: window frame with offset must specify both starting and ending bounds。
正确写法只有这几种组合:
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW(最常用) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING-
ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING(前3行中去掉最新1行,即取第2、第3行)
CURRENT ROW 可以省略不写,比如 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 等价于 ROWS BETWEEN 2 PRECEDING AND(注意结尾的 AND 不能丢),但初学者建议显式写出,避免歧义。
ORDER BY 不是可选,而是强制前提
没有 ORDER BY 的 ROWS BETWEEN 在 PostgreSQL 中虽不报错,但窗口顺序由执行计划决定,结果完全不可复现——同一查询多次运行,CURRENT ROW 可能指向不同物理行,导致移动平均值漂移。
更隐蔽的问题是:仅靠 ORDER BY sale_date 不够。如果多条记录日期相同(比如批量导入的订单),PostgreSQL 不保证这些行之间的相对顺序,2 PRECEDING 可能每次框选不同两行。
稳妥做法:
- 加二级排序,例如
ORDER BY sale_date, id或ORDER BY sale_date, created_at - 若时间字段精度低(如只有日期),先用
ROW_NUMBER() OVER (ORDER BY sale_date, id)生成稳定序号,再按该序号开窗 - 避免
ORDER BY sale_date DESC做倒序滑动——PRECEDING在逻辑上会变成“更晚的时间”,极易混淆
ROWS BETWEEN 数的是“行号”,不是“天数”或“值范围”
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW 意味着“取当前行往上数6行 + 当前行”,总共7行——跟日期是否连续、有没有空缺完全无关。如果某天没数据,那一行就不存在,窗口自然少一行。
这意味着:
- 想算“过去7天滚动均值”,不能直接套用这个写法,否则缺日会导致窗口实际覆盖不到7天
- 要真正实现时间语义的滚动,得先补全日期(用
GENERATE_SERIES()),再左连接原始数据,最后在完整序列上开窗 - 同一天有100条订单?
ROWS BETWEEN 6 PRECEDING仍只取物理上前6行,不会因时间重复而膨胀;而RANGE BETWEEN则会把这100行全拉进来
所以,ROWS 的优势是可控,劣势是脱离时间逻辑——用之前得先确认你的业务到底要“最近N条记录”,还是“最近N天的数据”。
PARTITION BY 漏掉就全表混算,指标直接翻车
分组内计算移动平均时,PARTITION BY 是硬性要求。漏掉它的典型后果是:用户A的第一笔订单,窗口里混进了用户B最近6笔订单,均值完全失真。
正确结构必须是:
AVG(amount) OVER ( PARTITION BY user_id ORDER BY order_date, id ROWS BETWEEN 6 PRECEDING AND CURRENT ROW )
注意点:
-
PARTITION BY必须放在ORDER BY之前,顺序不能反 - 每个分区内独立计行号,
6 PRECEDING指该用户自己的前6行,不会跨用户 - 如果分区数据量极小(比如某用户只有2笔订单),第1、2行的窗口分别只有1、2行,
AVG()仍正常计算,不会报错或补零
最容易被忽略的是:PARTITION BY 字段如果有 NULL,PostgreSQL 会把所有 NULL 值归为同一组——如果你的 user_id 允许为空,这部分数据会被错误聚合到一起,必须提前处理。










