SQL窗口函数定义解析(详解窗口函数与聚合函数的核心区别及OVER用法)

SQL 的窗口函数(Window Function)是一类”跨行计算但保留明细”的函数,它借助 OVER() 子句在每行上附加一段相关行的统计或排序结果,而不像 GROUP BY 那样把多行压缩成一行。聚合函数搭配 GROUP BY 后,每组只返回一条汇总记录、原始明细消失;窗口函数则让每行都保留,并在行尾追加一份分组上下文。据 ConversationalSQL 的概括,窗口函数”在保留每一行的同时对其做汇总”,这正是它区别于聚合函数的根本。MySQL 8.0+、SQL Server、PostgreSQL、Oracle 均支持窗口函数。

一、窗口函数的本质:加上下文,不折叠行

聚合函数的目标是”收口”——按 GROUP BY 把同组行合并成一个汇总行。窗口函数相反:它定义一个”窗口”(一组相关行),在窗口内计算并把结果贴回每一行,行数始终不变。

据 TutorialReference 的入门示例,下面两条语句算的是同一个”全员薪资总和”,结果形态却完全不同:

sql

-- 聚合:5 行变 1 行,明细消失
SELECT SUM(salary) AS total_salary FROM employees;

-- 窗口:5 行仍在,每行都带上总和 370000
SELECT name, department, salary,
       SUM(salary) OVER() AS total_salary
FROM employees;

OVER() 是窗口函数的标志。空 OVER() 表示把整张结果集当一个窗口;加上 PARTITION BY 则把数据切成多个分区,在每个分区内独立计算。

二、OVER() 与 PARTITION BY 的拆解

OVER() 里可放两类定义:

  • PARTITION BY 列:等价于 GROUP BY 的”分组”逻辑,但行不折叠。窗口函数把数据分成若干”区”,在每个区内分别计算,如同每个组是一张小表。
  • ORDER BY 列:决定区内行的顺序,是 ROW_NUMBER()、累计求和、滑动平均等有序计算的前提。
sql

SELECT name, department, salary,
       SUM(salary) OVER (PARTITION BY department) AS dept_total
FROM employees;

据 sqltest.online 的解析,PARTITION BY 非必填——省略时整表为一个分区,适合做全表排名或总体指标。WHERE 仍先于窗口计算执行,先过滤行再做窗口,逻辑与 GROUP BY 一致。

三、窗口函数与聚合函数的核心差异

下表综合 SegmentFault、TutorialReference、CodeGym 的对照(这是本题的核心):

对比维度 聚合函数(GROUP BY) 窗口函数(OVER)
行数变化 分组合并,行数减少 行数不变,明细全部保留
分组语法 GROUP BY OVER(PARTITION BY)
组内排序 无法直接排序 支持 ORDER BY 区内排序
滚动/累计计算 无法实现累计求和、移动平均 支持 ROWS/RANGE 滚动窗口
排名能力 无 专用 ROW_NUMBER/RANK/DENSE_RANK
与明细列共存 仅分组列可共存 始终可与任意明细列共存
使用位置 SELECT/HAVING 仅 SELECT 列表

SegmentFault 用一个 sales 表直观展示:聚合 GROUP BY dept 只剩 2 行汇总;窗口 SUM(money) OVER(PARTITION BY dept) 则 3 行明细与部门总额并存。

四、聚合函数也能当窗口函数用

多数聚合函数(SUM/AVG/MIN/MAX/COUNT)既可配 GROUP BY 也可配 OVER()——函数本身不变,行为变。据 ConversationalSQL 举例,用 MIN()/MAX() 作窗口函数,可在每行带上”同作者最早/最晚创作年份”:

sql

SELECT Title, Artist, YearCreated,
       MIN(YearCreated) OVER (PARTITION BY Artist) AS EarliestYear,
       MAX(YearCreated) OVER (PARTITION BY Artist) AS LatestYear
FROM Gallery.Paintings;

每行保留自身年份,同时携带该作者的区间极值,无需额外子查询或连接。

五、窗口函数独有:排名与框架

聚合函数做不到的两类能力,是窗口函数的价值高地:

排名函数。ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE() 只在窗口语义下存在,用来在区内给行编号或分桶。CodeGym 展示了一行内同时算”区域总额”与”区域销售排名”:

sql

SELECT region, city, sales,
       SUM(sales) OVER (PARTITION BY region) AS total_sales,
       RANK() OVER (PARTITION BY region ORDER BY sales DESC) AS sales_rank
FROM sales_data;

窗口框架(Window Frame)。通过 ROWS/RANGE 定义窗口内”当前行的前后范围”,实现累计求和、移动平均。没有 ORDER BY 时窗口默认覆盖整个分区;有 ORDER BY 时默认框架为”从分区首行到当前行”,这正是累计求和的实现基础。

六、选型:何时用窗口、何时用聚合

据 CodeGym 与 sqltest.online 的总结,按意图二选一:

  • 用 GROUP BY/聚合:目标是生成最终汇总报表(月度总额、分类订单数),要的是更少的行。
  • 用窗口函数:要保留全部明细还要附加分析(每行占比、组内排名、累计值),或需排名与滚动计算。

一个判断口诀来自 ConversationalSQL:”我既想要这个总数,又不想丢掉行”——这就是窗口函数的场景。

常见问题(FAQ)

窗口函数一定要写 PARTITION BY 吗?
不必须。省略时整表为一个分区,常用于全表排名或总体指标。

窗口函数能写在 WHERE 里吗?
不能,窗口函数只能出现在 SELECT 列表(及少数 ORDER BY);要按窗口结果过滤,需用子查询或 CTE 包一层。

聚合函数和窗口函数哪个更快?
窗口函数通常多扫一次数据且不折叠,开销略高;但免去自连接/子查询时往往更优,应以 EXPLAIN 为准。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
小码农的头像小码农认证作者

相关推荐

返回顶部