MIN(value) OVER (ORDER BY date) 返回的是从首行到当前行的滚动最小值,并非全局历史最低值;默认RANGE框架在日期重复时会合并多行导致漏判,必须显式用ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW并配合唯一排序键(如date, id)确保逐行稳定计算。

MIN OVER 为什么查不到真正的历史最低值
直接写 MIN(value) OVER (ORDER BY date) 看似合理,但实际返回的是「从第一行到当前行」的最小值,不是严格意义上的「历史最低」——如果数据里有空值、重复日期或未排序的输入,结果会错。关键是窗口框架默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,它依赖排序键的语义,而日期字段若存在重复,RANGE 可能合并多行导致漏判。
正确写法必须显式指定 ROWS 框架
用 ROWS 替代默认的 RANGE,才能确保逐行累积计算,不因日期重复而跳过记录:
SELECT date, value,
MIN(value) OVER (ORDER BY date, id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS hist_min
FROM stock_price;
-
date, id组合作为排序键,避免日期相同时窗口行为不确定 - 必须写
ROWS,否则 PostgreSQL/Oracle 默认用RANGE,SQL Server 则默认ROWS,跨数据库时容易出错 - 如果表本身没主键或唯一序号,可临时加
ROW_NUMBER() OVER (ORDER BY date)当作id用
NULL 值会让 MIN OVER 返回 NULL,不是你想要的“最低”
MIN() 遇到全 NULL 子集时返回 NULL,但业务上往往希望忽略 NULL 继续找非空最小值。这时不能只靠窗口函数本身:
- 先用
COALESCE(value, 999999)填充临时极大值(需确保不干扰真实数据范围) - 更稳妥的做法是用
FILTER (WHERE value IS NOT NULL)(PostgreSQL 9.4+)或子查询预过滤 - MySQL 8.0 不支持
FILTER,得用CASE WHEN value IS NOT NULL THEN value END配合MIN()
性能隐患:ORDER BY 字段没索引会很慢
窗口函数的 ORDER BY 如果落在无索引列上,特别是千万级表,执行计划可能触发全局排序,CPU 和临时空间飙升:
- 确认
EXPLAIN输出里有没有WindowAgg跟着Sort节点 - 在
ORDER BY的字段(如date)上建 B-tree 索引,对时间序列类查询提升明显 - 如果只查最近 N 天的历史最低,别用窗口函数,改用
SELECT MIN(value) FROM t WHERE date 更快
真正麻烦的不是语法,而是窗口边界和 NULL 处理的组合效应——同一句 SQL 在不同数据库、不同数据分布下可能给出不同结果。

















