应显式写为max(val) over (order by ts rows between unbounded preceding and current row),因不同数据库默认窗口帧不同:postgresql用range(同值行全纳入),sql server/oracle/mysql用rows(仅物理前序行),排序列重复时range会导致“提前跳变”,违背累积逻辑;加唯一辅助列如order by ts, id可确保严格顺序。

SQL中MAX() OVER怎么写才能拿到“当前行及之前所有行”的最大值
直接用 MAX(column) OVER (ORDER BY key) 就行,但必须显式声明窗口帧(frame),否则在不同数据库里行为不一致——PostgreSQL 默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,而 SQL Server 和 Oracle 默认是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,MySQL 8.0+ 也按 ROWS 处理。不写明帧,逻辑可能意外出错。
关键点:只要排序列(ORDER BY)唯一且业务语义清晰,ROWS 和 RANGE 在这种场景下结果通常一致;但如果排序列有重复值,RANGE 会把同值的所有行一起纳入计算,而 ROWS 只按物理顺序往前取——多数人想要的是“时间/序号递增下的累积最大值”,所以推荐明确写成:
MAX(val) OVER (ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)- 其中
ts是你用来定义“先后”的列(比如时间戳、自增ID) - 如果
ts可能重复,又希望严格按插入顺序(避免并列导致跳变),得加个唯一辅助列,例如ORDER BY ts, id
为什么MAX() OVER (ORDER BY ...)不加ROWS有时结果不对
问题常出现在 PostgreSQL:它默认用 RANGE 帧,当排序键重复时,会把所有相同 ts 的行视为同一“组”,一起参与当前行的窗口计算。比如三行 ts = '10:00',第二行算 MAX 时,其实已包含第三行的值——这违背“只看当前行及之前物理行”的直觉。
典型错误现象:MAX 值在重复时间点上“提前跳变”,后续某行的累积最大值比前一行还小(因为窗口回退时漏掉了中间值)。
- MySQL / SQL Server 不会这样,默认就是
ROWS,但靠默认不保险 - 显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW是跨库安全写法 - 用
RANGE的唯一合理场景是做“按值分段的累计”,比如按价格区间滚动最高价,而非按时间顺序
MAX OVER动态范围和普通子查询性能差多少
窗口函数基本是单趟扫描,复杂度 O(n);等价的关联子查询(如 (SELECT MAX(t2.val) FROM t t2 WHERE t2.ts )在无合适索引时是 O(n²),数据量过万就明显卡顿。
- 确保
ORDER BY列上有索引(如CREATE INDEX idx_ts ON tbl(ts)),窗口函数能利用索引加速排序与帧定位 - 某些旧版 MySQL(
- 如果要兼容极老环境,宁可用临时表 + 自连接 + 索引,也别依赖用户变量
遇到NULL值,MAX() OVER怎么处理
MAX() 本身会忽略 NULL,返回非空最大值;但如果窗口内所有值都是 NULL,结果就是 NULL。这和聚合函数行为一致,但容易被误认为计算失败。
- 若需把
NULL当 0 处理,先用COALESCE(val, 0)再套窗口函数 - 若想跳过
NULL行不参与排序(比如它们没有有效时间),应在WHERE中过滤,而不是靠窗口函数“自动跳过” - 注意:排序列本身为
NULL会导致该行被排到最前面或最后面(取决于NULLS FIRST/LAST),影响窗口边界
ROWS 帧的显式声明——这两点不确认清楚,同样的 SQL 在不同库、不同数据分布下,结果可能完全不同。










