SQL 为什么窗口函数是高级 SQL 分水岭?

12次阅读

窗口函数让“既要聚合、又要明细”成为可能,通过OVER()子句在不丢失原始行的前提下实现分组聚合与排序计算,支持LAG/LEAD环比、ROWS BETWEEN滚动计算等高级分析,是高级sql能力的分水岭。

SQL 为什么窗口函数是高级 SQL 分水岭?

窗口函数让“既要聚合、又要明细”变成一句废话

传统 GROUP BY 一执行,原始行就没了——想看每个员工的工资,又想看他们部门平均工资?得写子查询或自连接。窗口函数直接在 select 里加个 AVG(salary) OVER (PARTITION BY dept),每行都带着自己部门的均值回来,不丢数据、不增行数。

  • 常见错误:把 OVER() 漏写或写成空括号,结果变成全表窗口(比如全公司平均),而不是按部门算
  • ORDER BY 在窗口里不是可选的:用 ROW_NUMBER() 或计算累计和时,没 ORDER BY 会报错或返回不可靠结果
  • mysql 8.0+、postgresql、SQL Server 2012+、oracle、Doris、StarRocks 都支持;但 sqlite(除非是最新 dev 版)和旧版 hive 不支持

LAG()LEAD() 是环比/同比的底层开关

做月度销售环比,不用再拼日期逻辑 + 左连接上个月表。一行 LAG(amount) OVER (PARTITION BY region ORDER BY sale_month) 就能拿到上月值,再跟当前行做减法除法就行。

  • 容易踩坑:如果 sale_month 有缺失(比如某地区某月没数据),LAG() 会跳过空值取更早的,导致“上月”错位——建议先用 GENERATE_SERIES 或补全月份再开窗
  • LAG(col, 2) 表示取前两行,不是“上个月”,顺序完全依赖 ORDER BY 的列和方向
  • 性能上比自连接快 2–3 倍(实测百万级订单表,窗口函数耗时约 800ms,等效自连接 2.5s)

ROWS BETWEEN 控制移动窗口,不是玄学

算 7 日滚动销售额?不是靠应用层循环,而是数据库原生支持:SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)。这里 ROWS 看的是物理行序,RANGE 看的是值范围(比如 “过去 7 天” 要用 RANGE INTERVAL '6 days' PRECEDING,但仅 PostgreSQL/Oracle 支持)。

  • MySQL 不支持 RANGE + INTERVAL,只能用 ROWS + 排序后硬凑天数,遇到重复日期可能不准
  • UNBOUNDED PRECEDING 是安全写法,但别滥用——没 PARTITION BY 时,它会让窗口跨全表,大表易 OOM
  • 排序字段必须有索引,否则 ORDER BY + 窗口框架会触发 filesort,拖慢几倍

为什么它是高级 SQL 的分水岭?

因为能否用好窗口函数,直接暴露你是在写“能跑就行”的 SQL,还是在构建可维护、可复用、可下推的数据逻辑。它不解决“查不到”的问题,而是解决“查出来没法直接用”的问题——比如运营要一份带排名、带累计、带环比、还保留用户 ID 的明细报表,不用窗口函数就得四张临时表嵌套三层子查询。而一个写对的 OVER() 子句,就是把这四步压进一行表达式里。

真正卡住人的从来不是语法,而是想清楚:我要的“窗口”,到底该按什么分(PARTITION BY)、按什么排(ORDER BY)、框多大(ROWS/RANGE)。这三个条件少一个,结果就偏一点;错一个,整张报表就废掉。

text=ZqhQzanResources