主页 > 精品文章 > 数据库 > SQL 窗口函数 OVER 使用详解

标签推荐

>> 更多

SQL 窗口函数 OVER 使用详解

 2026-02-15
没写 partition by 时 over() 将全表视为一个分区,row_number() 全局编号,avg() 计算全表均值;order by 决定窗口函数语义,缺省导致错误或未定义行为;rows 按行数、range 按值范围切窗;where 在窗口计算前执行。

sql 窗口函数 over 使用详解

OVER() 里不写 PARTITION BY 就是全表当一个分区

很多人以为 OVER() 默认按某种逻辑分组,其实不是——没写 PARTITION BY,就等价于把整张表当作一个大分区。这意味着 ROW_NUMBER() 会从 1 排到总行数,AVG(salary) OVER() 算的是全表平均,而不是按部门或时间滚动。

常见错误现象:SELECT name, salary, AVG(salary) OVER() FROM emp 返回每行都带同一个平均值,但你以为它“自动按部门算了”。

  • 想按部门算平均薪资?必须显式写 AVG(salary) OVER(PARTITION BY dept_id)
  • 想保留原始行顺序再编号?ROW_NUMBER() OVER(ORDER BY id) 可以,但没 PARTITION BY 就不是“每个部门内重排”
  • 性能影响:全表窗口(无 PARTITION BY)在大数据量时可能比带分区的更慢,因为无法利用分区剪枝

ORDER BY 在 OVER() 中不是可选的,而是决定“累积行为”的开关

ORDER BY 放在 OVER() 里,不只是为了排序结果,它直接改变窗口函数的语义。比如 SUM(sales) OVER(ORDER BY date) 是累加,而 SUM(sales) OVER(PARTITION BY year ORDER BY date) 是每年内逐日累加。

容易踩的坑:RANK()ROW_NUMBER() 如果漏了 ORDER BY,会报错(PostgreSQL/SQL Server 报错;MySQL 8.0+ 允许但行为未定义,实际返回随机序)。

  • 需要“当前行及之前所有行”的聚合?必须带 ORDER BY,否则默认是整窗(ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  • 想算移动平均(比如最近 7 天)?得配合 ROWS BETWEEN 6 PRECEDING AND CURRENT ROW,光靠 ORDER BY 不够
  • 注意 NULL 处理:ORDER BY col 默认把 NULL 排最前(SQL 标准),但不同数据库可配置,MySQL 8.0+ 和 PostgreSQL 行为一致,SQL Server 默认 NULL 最后

ROWS vs RANGE:边界定义方式不同,结果可能差很多

ROWS 按物理行数切窗口,RANGE 按排序值的逻辑范围切。对时间序列或金额类数据,用错会导致计算结果完全偏离预期。

典型场景:每日销售额,想算“截止当天的累计和”。如果用 SUM(sales) OVER(ORDER BY sale_date RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),同一天多条记录会被合并进同一窗口;而 ROWS 版本则严格按行序累加,哪怕日期相同也逐行算。

  • 日期字段有重复值?优先用 ROWS 避免意外聚合
  • 需要“值相等即视为同一级”(比如按分数分等级排名),RANGE 更符合语义,但注意它只支持单个 ORDER BY 表达式且必须是数值或日期类型
  • PostgreSQL 支持 RANGEROWS,MySQL 8.0+ 仅支持 ROWSRANGE 语法存在但被忽略),SQL Server 同样只认 ROWS

WHERE 和 OVER() 的执行顺序:过滤发生在窗口计算之后

这是最容易误解的一点:WHERE 条件在窗口函数执行前就已过滤掉数据,所以 OVER(PARTITION BY dept_id) 只会在 WHERE 留下的行中分组,不会“先分组再过滤”。

但如果你写了 HAVING 或想基于窗口结果再过滤(比如只看排名前 3 的人),就不能用 WHERE,得套一层子查询或 CTE。

  • 错误写法:SELECT *, ROW_NUMBER() OVER(PARTITION BY dept_id ORDER BY salary DESC) rn FROM emp WHERE rn —— <code>rn 是窗口别名,不能在同级 WHERE 中引用
  • 正确做法:用 CTE 或子查询,例如 SELECT * FROM (SELECT *, ROW_NUMBER() OVER(...) rn FROM emp) t WHERE t.rn
  • 性能提示:先 WHEREOVER 通常更快;但如果过滤条件依赖窗口结果(如“工资高于部门平均”),就必须两步走,避免全表计算窗口后再丢弃

事情说清了就结束

 

版权声明: 本站资源均来自互联网或会员发布,如果侵犯了您的权益请与我们联系,我们将在24小时内删除!谢谢!联系QQ:76900276

转载请注明: SQL 窗口函数 OVER 使用详解

嘿,我来帮您!