
MAX OVER 不能直接用于历史最高价统计
直接写 MAX(price) OVER (PARTITION BY product_id ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 看似合理,但实际会出错——MySQL 8.0.2 之前不支持 ROWS BETWEEN 与 ORDER BY 同时出现在窗口函数中(报错 Window 'w' with RANGE or ROWS frame requires an ORDER BY clause,但即使加了 ORDER BY,旧版本仍拒绝解析)。更重要的是,**“历史最高价”本质是累积最大值,不是分组后静态聚合**,必须依赖支持累积语义的窗口帧。
正确写法:用 RANGE UNBOUNDED PRECEDING 或适配 MySQL 版本
关键在帧定义要明确表达“从第一条到当前行”。不同数据库语法略有差异:
- MySQL 8.0.2+、PostgreSQL、SQL Server:用
MAX(price) OVER (PARTITION BY product_id ORDER BY date RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 如果日期有重复(同一天多条记录),
RANGE可能导致同一时间点内所有行得到相同最大值(因 RANGE 按值归并);此时应改用ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,确保严格按物理顺序累积 - SQLite 3.25+ 支持窗口函数,但不支持
RANGE帧,只能用ROWS - 旧版 MySQL(
容易踩的坑:NULL、排序字段缺失、分区键错位
这三个问题会让结果完全失真,且不易一眼发现:
-
price字段含NULL:MAX 会跳过 NULL,但如果首条记录就是 NULL,后续非空值的“历史最高”会错误地从第二条开始算——建议先用COALESCE(price, 0)或过滤掉 NULL 行 - 没写
ORDER BY date:窗口函数失去时序依据,MAX变成对每个product_id全量取最大,等价于普通分组聚合,不是“历史”而是“至今总最高” - 误把
product_id写成category_id分区:导致跨商品混算,比如商品 A 和 B 被强行归到同一组比价格 - 日期字段类型是
STRING(如 '2023-01-01')但未标准化:可能因字典序导致 '2023-10-01' 排在 '2023-02-01' 前——务必确认date是DATE或DATETIME类型
替代方案:当窗口函数不可用时怎么处理
比如在 MySQL 5.7 或某些嵌入式 SQLite 场景下,只能绕开窗口函数:
- 用相关子查询:
(SELECT MAX(t2.price) FROM sales t2 WHERE t2.product_id = t1.product_id AND t2.date ,但数据量大时极慢 - 用变量模拟累积(MySQL 5.7):
@max_price := IF(@prev = product_id, GREATEST(@max_price, price), price) AS history_max,需严格按product_id, date排序,且变量行为在复杂查询中不稳定 - 应用层处理更稳妥:查出按
product_id, date排序的全量数据,在 Python/Java 中用字典维护每个商品的当前最高价,边遍历边更新——逻辑清晰、可控性强
真正麻烦的不是语法,而是“历史最高”这个业务含义在不同系统里隐含的时间粒度、去重规则、NULL 处理策略——这些不会写在 SQL 里,但会决定结果对不对。

















