窗口函数只能出现在SELECT列表或ORDER BY中,不能用于WHERE、GROUP BY、HAVING;因其在这些子句执行后才计算,典型错误是误将其用于过滤或分组。

窗口函数写在 SELECT 里,不能放 WHERE 或 GROUP BY 后面
很多人一写 ROW_NUMBER() 或 RANK() 就报错,常见原因是把它当成了普通聚合函数去用。窗口函数本质是“每行算一个值”,不是“把多行压成一行”,所以它只能出现在 SELECT 列表或 ORDER BY 中,绝不能进 WHERE、GROUP BY、HAVING —— 这些子句执行时,窗口计算还没发生。
典型错误现象:ERROR: window functions are not allowed in WHERE;或者写完 GROUP BY 再套 ROW_NUMBER(),结果发现序号全变成 1。
- 想筛出“每个部门薪资前 3 的人”?先用窗口函数生成序号,再用外层查询过滤
rn - 想按时间排序后取“最近 3 笔订单”?
ORDER BY order_time DESC必须写在OVER()里,不是外面的ORDER BY - MySQL 8.0+、PostgreSQL、SQL Server 都支持标准语法;但 SQLite 默认不启用窗口函数(需编译选项)
PARTITION BY 不等于 GROUP BY:它不压缩行数,只重置计算上下文
PARTITION BY 看起来像分组,但它只是告诉数据库:“在这个分区内部独立跑一遍窗口逻辑”,原始行一条不少。而 GROUP BY 是真合并——这点混淆直接导致业务逻辑错乱,比如误以为加了 PARTITION BY dept_id 就能直接拿到各部门平均薪资,其实还得配合 AVG() OVER(...) 才行。
使用场景举例:给用户行为日志打“本次会话第几次点击”,就得 PARTITION BY user_id ORDER BY click_time;如果漏掉 ORDER BY,序号就是随机的。
-
PARTITION BY字段必须是查询中实际存在的列,不能是表达式(如PARTITION BY YEAR(order_date)在某些旧版 MySQL 会报错) - 多个字段分区写成
PARTITION BY a, b,不是PARTITION BY (a, b) - 性能上,大表加复杂
PARTITION BY + ORDER BY可能触发磁盘临时表,注意检查EXPLAIN中的Using filesort和Using temporary
ORDER BY 在 OVER() 里决定“窗口内顺序”,影响 ROW_NUMBER/RANK/DENSE_RANK 结果
没写 ORDER BY 的 ROW_NUMBER() OVER(PARTITION BY dept_id),不同数据库行为不一致:PostgreSQL 按物理存储顺序排,MySQL 8.0 可能报错或返回不确定结果。业务上一旦依赖“最新”“最早”“最高”,就必须显式声明排序依据。
常见错误现象:用 RANK() 统计销售额排名,但没指定 ORDER BY amount DESC,结果排名全是 1;或者用 LAG() 取上一笔订单时间,却忘了按用户和时间双重排序,导致跨用户取值。
-
ROW_NUMBER()总是编号 1,2,3…;RANK()并列时跳号(如 1,1,3);DENSE_RANK()并列不跳号(如 1,1,2)——选哪个取决于业务定义的“并列是否占名额” -
LAG(col, 1)默认取前 1 行,但第一行没有前驱值,返回 NULL;可加第三个参数设默认值,如LAG(amount, 1, 0) - 涉及时间字段排序时,务必确认时区和精度:同秒级时间戳可能因毫秒差异导致排序不稳定
嵌套窗口函数不合法,但可以用 CTE 或子查询拆解
SQL 标准禁止在窗口函数的参数里再写窗口函数,比如 ROW_NUMBER() OVER(ORDER BY SUM(sales) OVER(PARTITION BY region)) 是语法错误。这不是数据库限制,是 SQL 解析规则本身不允许。
真实业务常需要“先算部门总销,再按总销排名”,这时必须分步:先在子查询或 CTE 里算出聚合值,再在外层对这个结果应用窗口函数。
- CTE 更易读:
WITH dept_sum AS (SELECT dept_id, SUM(sales) AS total FROM orders GROUP BY dept_id) SELECT *, RANK() OVER(ORDER BY total DESC) FROM dept_sum - 子查询也行,但嵌套深了容易括号错位;注意别漏写别名,否则外部引用会报
column not found - 避免在 CTE 里写
SELECT *后又加窗口函数——列名冲突或顺序错乱会导致不可预期结果
事情说清了就结束。窗口函数真正难的不是语法,而是想清楚“我要的‘当前’到底指哪几行”,这个边界一旦画错,后面全错。










