滑动平均值必须用 rows between,仅 order by 默认按值分组(range),导致同时间戳数据被错误聚合;需显式指定 rows between n preceding and current row 并确保 order by 列具有确定性排序,否则结果不可预测。

滑动平均值必须用 ROWS BETWEEN,不能只靠 ORDER BY
只写 ORDER BY 的窗口函数(比如 AVG(col) OVER (ORDER BY ts))默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,它按值分组聚合——如果时间戳有重复,同秒内所有记录会一起参与计算,结果不是你想要的“最近 N 行”平均值。要严格按物理行序滚动,ROWS BETWEEN 是唯一可靠方式。
常见错误现象:AVG(price) OVER (ORDER BY event_time) 在高频日志中输出突变或平台式平均值,尤其当 event_time 精度为秒、且每秒多条记录时。
- 必须显式声明
ROWS BETWEEN n PRECEDING AND CURRENT ROW,例如计算 5 行滑动平均:AVG(val) OVER (ORDER BY ts ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) -
n PRECEDING中的数字是“往前数几行”,所以 5 行窗口对应4 PRECEDING(当前行 + 前 4 行 = 5 行) - 若需对称窗口(如前后各 2 行),写成
ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING
ORDER BY 列必须有确定性排序,否则 ROWS BETWEEN 行为不可预测
数据库按执行计划决定“前几行”是谁,如果 ORDER BY 列存在重复且无第二排序键,同一查询多次运行可能返回不同结果——不是 bug,是标准允许的未定义行为。
使用场景:金融 tick 数据、IoT 传感器采样、用户点击流,这些数据常在毫秒级时间戳下仍有并发写入导致顺序模糊。
- 补救办法:加一个唯一列兜底,例如
ORDER BY event_time, id或ORDER BY event_time, row_number() OVER ()(后者慎用,部分引擎不支持嵌套窗口) - PostgreSQL 和 SQL Server 支持
ORDER BY ts, ctid(ctid是物理位置标识符),MySQL 8.0+ 可用ORDER BY ts, json_extract(_rowid, '$')(需开启隐藏主键) - 避免用
ORDER BY RAND()或无索引列排序,会导致全表扫描 + 临时排序,性能断崖式下降
空值和边界行处理:默认跳过 NULL,首几行结果行数不足
AVG() 窗口函数默认忽略 NULL 值,但行计数仍按 ROWS BETWEEN 物理框定——这意味着第 1 行永远只有 1 个有效值参与平均,第 2 行最多 2 个,依此类推。不会自动补足 5 行才开始计算。
错误认知:“窗口大小设为 5,前 4 行应该返回 NULL”。实际是前 4 行分别用 1–4 行非 NULL 值计算平均,除非显式过滤。
- 想让前
n-1行返回NULL?加条件判断:CASE WHEN ROW_NUMBER() OVER (ORDER BY ts) - 想跳过含 NULL 的整行(不参与任何窗口计算):在
WHERE子句提前过滤,而非依赖窗口函数内部忽略 - 想把 NULL 当 0 参与计算?用
COALESCE(val, 0),但注意这会改变统计意义——均值不再反映真实非空样本
性能关键:ORDER BY 列必须有索引,且避免在大偏移窗口中用 FOLLOWING
带 FOLLOWING 的窗口(如 ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)要求数据库预读后续行,在流式场景或大数据集上内存占用陡增,Spark SQL 和某些 MySQL 版本会直接报 Window frame with FOLLOWING is not supported 错误。
真实瓶颈往往不在 AVG() 计算本身,而在排序和帧定位。一张千万行日志表,没索引的 ORDER BY event_time 可能触发 >2GB 临时磁盘排序。
- 务必确保
ORDER BY列已建索引,复合索引优先:例如INDEX (device_id, event_time)用于按设备分组的滑动平均 - 尽量用
PRECEDING + CURRENT ROW单向窗口,它可流式计算,多数引擎支持物化中间结果复用 - ClickHouse 用户注意:
ROWS BETWEEN在MergeTree表上极快,但必须配合PARTITION BY和ORDER BY建表逻辑,否则退化为全局排序
真正难的不是写对语法,而是确认你的 ORDER BY 列在业务意义上“行序即时间序”,且数据库真的按你设想的方式落盘和索引——这两点出问题,再标准的 ROWS BETWEEN 也救不回结果的正确性。










