应使用窗口函数max() over(partition by...)而非group by,以保留每行数据并计算其与组内最大值的差;该语法支持mysql 8.0+等主流数据库,但不支持sqlite及mysql 5.7。

GROUP BY 不能直接算“每行和组内最大值的差”
想让每条记录显示它离自己所在分组最大值还差多少,GROUP BY + MAX() 行不通——因为 GROUP BY 会把多行聚合成一行,原始行数据就没了。你真正需要的是窗口函数,不是聚合函数。
必须用 MAX() OVER(PARTITION BY ...) 才能保留原行
MAX(col) OVER (PARTITION BY group_col) 是唯一正确路径:它按 group_col 分组计算最大值,但不压缩行数,每行都能拿到本组的 MAX(col) 值,再跟自己当前值相减即可。
- 写法固定:
MAX(value) OVER (PARTITION BY user_id ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)——ORDER BY和ROWS其实可省略,因为默认就是全组范围;但显式写出来更防错 - 别漏
PARTITION BY,否则变成全表最大值,所有行都减同一个数 - MySQL 8.0+、PostgreSQL、SQL Server 2012+、BigQuery 都支持;SQLite 和 MySQL 5.7 不支持,得换思路(比如自关联或变量模拟)
- 字段类型要支持减法,比如数值、日期;
TEXT或JSON类型直接报错
常见错误:在 WHERE 或 GROUP BY 里用窗口函数
窗口函数只能出现在 SELECT 和 ORDER BY 子句中,不能用于 WHERE(执行顺序早于窗口计算)、也不能放在 GROUP BY 后面再套一层——语法直接拒绝。
- 错误示例:
WHERE value → 报错,<code>WHERE看不到窗口结果 - 错误示例:
GROUP BY user_id HAVING MAX(value) - MIN(value) > 100→ 这是极差,不是“每行对组内最大值的差距” - 如果真要过滤差值,得先算出列,再嵌套查询:
SELECT * FROM (SELECT *, MAX(v) OVER (...) - v AS gap FROM t) t2 WHERE gap > 10
NULL 处理和性能注意点
MAX() OVER 自动忽略 NULL,但如果整组全是 NULL,结果也是 NULL;减法遇到 NULL 整体变 NULL,这点比聚合函数更敏感。
- 稳妥写法:
COALESCE(MAX(value) OVER (PARTITION BY id), 0) - COALESCE(value, 0),但要注意语义是否合理(比如把 NULL 当 0 可能歪曲业务) - 高基数分组(如上百万个
user_id)会让窗口计算变慢,建议先WHERE过滤掉不用的组,再开窗 - 如果只关心“最大值本身那行”,其实不需要窗口函数,用
ROW_NUMBER() OVER (PARTITION BY id ORDER BY value DESC)更轻量
实际最常被忽略的是执行顺序约束:窗口函数不是万能胶,它只活在 SELECT 阶段,往前一步(WHERE)看不见,往后一步(GROUP BY 后)又进不去。写之前先默念一遍 SQL 执行顺序。










