答案是必须加OVER子句。移动振幅需基于指定窗口(如ROWS BETWEEN N PRECEDING AND CURRENT ROW)逐行计算MAX-MIN,否则默认全表聚合,且须注意排序稳定性、NULL处理及数据库兼容性。

用 MAX() 和 MIN() 窗口函数算移动振幅,必须加 OVER 子句
移动振幅本质是「某窗口内最大值减最小值」,不能直接写 MAX(col) - MIN(col),否则会报错或返回全表聚合结果。窗口函数必须显式声明窗口范围,否则默认是 OVER()(即整个结果集),这通常不是你想要的。
常见错误现象:SELECT MAX(price) - MIN(price) FROM sales 只返回一个数;而加了 OVER (ORDER BY ts ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) 才能逐行计算最近3条记录的振幅。
-
ROWS BETWEEN N PRECEDING AND CURRENT ROW最常用,适合时间序列、流水类数据 - 若需包含未来数据(如对齐中心点),可用
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING - 注意:
RANGE模式在时间字段上可能产生意外行为(如相同时间戳被归为同一组),优先用ROWS
PostgreSQL / MySQL 8.0+ / SQL Server 都支持,但 SQLite 不行
SQLite 目前(v3.45)仍不支持窗口版 MAX() 和 MIN(),强行使用会报错 near "OVER": syntax error。如果你在做本地测试或嵌入式开发,得换思路——比如用自连接或子查询模拟窗口,但性能差很多。
其他主流数据库基本一致,但参数细节有差异:
- MySQL 8.0+ 要求
ORDER BY必须存在(哪怕逻辑上不需要),否则报错This function requires an ORDER BY clause - PostgreSQL 允许
OVER (),但此时窗口是整张表,慎用 - SQL Server 对
ROWS范围限制更松,支持UNBOUNDED PRECEDING写法
真实场景中,别忽略 NULL 值和排序键重复问题
如果原始数据里有 NULL,MAX() 和 MIN() 会自动跳过它们,一般没问题;但若整窗口全是 NULL,结果就是 NULL,可能导致后续计算中断(比如除以该振幅)。建议加 COALESCE(..., 0) 或前置过滤。
更大的坑是排序键(ORDER BY 列)重复时,窗口边界可能不稳定:
- 例如按
created_at排序,但多条记录时间戳完全相同 → 数据库可能任意打乱顺序,导致同一批数据每次运行结果不同 - 解决办法:在
ORDER BY后追加唯一列,如ORDER BY created_at, id - 或者用
ROW_NUMBER() OVER (...)预生成稳定序号再作为排序依据
示例:计算每条订单前5笔(含自身)的金额振幅
SELECT
order_id,
amount,
MAX(amount) OVER (
ORDER BY created_at, order_id
ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
) -
MIN(amount) OVER (
ORDER BY created_at, order_id
ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
) AS amp_5
FROM orders;这里用了 created_at, order_id 双排序确保稳定性;4 PRECEDING + CURRENT = 5 行;结果列 amp_5 是逐行更新的移动振幅。如果某行前面不足4条记录(比如第一条),窗口自动收缩为实际存在的行数,不用担心越界。
真正难的不是写法,而是想清楚“窗口该按什么排序、要不要去重、NULL 怎么兜底”——这些决定了结果是否可复现、可解释。

















