窗口函数必须用OVER子句,否则MySQL 8.0直接报错;其本质是单次扫描替代多次子查询,依赖PARTITION BY和ORDER BY定义计算边界,不可省略。

必须用 OVER 子句,否则直接报错 ERROR 1064;窗口函数不是语法糖,是单次扫描替代多次子查询的底层能力。
为什么 ROW_NUMBER() 不能裸写,一写就报错
MySQL 8.0 不允许省略 OVER。像 ROW_NUMBER() 这类函数没有 OVER 就不是合法表达式,解析器直接拒绝——这不是兼容性问题,是语法强制要求。
根本原因在于:窗口函数不折叠行,它依赖明确的计算边界。没 PARTITION BY 和 ORDER BY,数据库不知道“在谁里面排”“按什么顺序排”,结果必然不可控。
- 只写
ROW_NUMBER() OVER ()是合法的,但等价于全表无序编号,每次执行顺序可能不同 - 真正可用的写法必须带排序:
ROW_NUMBER() OVER (PARTITION BY dept_name ORDER BY salary DESC) - 如果业务要求“相同薪资并列且跳号”,得换
RANK();要“并列但不跳号”,用DENSE_RANK()
SUM() OVER 替代自连接累计求和时,ROWS 比 RANGE 更快更稳
累计销售额、滚动平均这类场景,传统做法常靠关联子查询或变量模拟,性能差且难调试。用 SUM(amount) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING) 一行解决,但关键在 ROWS。
RANGE BETWEEN 按值范围匹配(比如所有同一天的记录算作“同一行”),遇到重复日期会多算;ROWS BETWEEN 按物理行偏移,稳定、可预期、能走索引。
- 安全写法:
SUM(sales) OVER (ORDER BY sale_date, id ROWS UNBOUNDED PRECEDING)(补id防止日期重复) - 危险写法:
SUM(sales) OVER (ORDER BY sale_date RANGE UNBOUNDED PRECEDING)(日期重复时结果膨胀) - 滚动3日平均必须用:
AVG(sales) OVER (ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)(2 而非 3,含当前行共 3 行)
用 LAG() 算环比时,首行 NULL 不是 bug,是设计如此
LAG(sales, 1) OVER (ORDER BY month) 返回上月值,但第一个月份没有“上月”,所以返回 NULL。这不是异常,是语义正确表现。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
若后续要做除法或差值计算,不处理 NULL 会导致整行结果为 NULL(比如 sales / LAG(sales))。
- 必须显式兜底:
COALESCE(LAG(sales, 1) OVER (ORDER BY month), 0) - 更合理的是用 0 或前值填充,而非删掉首行——报表通常需要完整时间轴
- 注意
ORDER BY字段精度:仅用created_at可能因秒级重复导致LAG取错行,应补唯一字段如id
窗口函数不能出现在 WHERE 或 HAVING 中
这是最容易栽跟头的地方:你不能写 WHERE row_num = 1,因为窗口函数在逻辑执行顺序中晚于 WHERE。它属于 SELECT 阶段的计算,此时过滤早已完成。
正确做法永远是套一层子查询或 CTE:
- 错误:
SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t WHERE rn = 1 - 正确:
SELECT * FROM (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t) t1 WHERE rn = 1 - 更清晰:
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t) SELECT * FROM ranked WHERE rn = 1
另外,OVER 里的 ORDER BY 只影响窗口内计算顺序,不影响最终输出顺序;真要控制返回行序,主查询还得单独写 ORDER BY。










