在SQL数据处理工作中,分组统计是一种高频需求。无论是按地区汇总销售额、按部门计算平均绩效,还是按用户统计订单数量,开发者通常都会想到GROUP BY。然而传统的GROUP BY有一个明显的限制:它会把同一分组的多行数据压缩为一行,查询结果的行数等于分组数量,原始明细行会丢失。如果业务场景要求同时保留每一行明细数据以及该行所属分组的汇总值,例如在销售明细报表中既要看到每笔订单的金额,也要看到该地区的总销售额,就需要使用窗口函数。窗口函数可以在不合并行的情况下,为每一行计算分组级别的统计值,从而保留完整的原始数据集。
窗口函数并不是新的聚合函数,而是对聚合函数或其他分析函数的一种计算方式扩展。通过OVER子句,窗口函数能够定义数据行的分组范围、排序规则和计算窗口,使得每一行都可以携带分组统计信息。这种方式在BI报表、数据核对、排名分析等场景中非常实用。

窗口函数的基本语法与分组机制
窗口函数的核心语法由函数名和OVER子句两部分组成。函数名可以是SUM、COUNT、AVG等常规聚合函数,也可以是ROW_NUMBER、RANK等专用分析函数。OVER子句用于定义窗口的计算范围,基本格式如下:
函数名(字段) OVER (
PARTITION BY 分组字段
ORDER BY 排序字段
窗口帧定义
) AS 别名
其中,PARTITION BY负责指定分组维度,它的作用与GROUP BY类似,但不会合并数据行。ORDER BY用于定义分组内的排序规则,只有涉及累计、排名等顺序相关的计算时才需要使用。窗口帧定义用来约束每一行参与计算的行集合,默认情况下,如果不指定窗口帧,聚合函数会计算整个分区的所有行。
需要特别说明的是,窗口函数与普通聚合函数在求值方式上存在本质区别。普通聚合函数在GROUP BY查询中只返回每个分组一行结果,而窗口函数会在每一行上独立执行,但计算范围由OVER子句中的分区和帧参数控制。因此,即使同一分区的不同行,窗口函数的结果也可能不同,这取决于窗口帧是否随当前行移动。例如,当使用ORDER BY和ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW时,窗口帧就会从分区起始行一直延伸到当前行,从而实现累计计算。
聚合函数结合窗口函数的分组统计示例
为了更直观地说明窗口函数如何在不改变行数的前提下完成分组统计,下面以一张销售记录表sales为例。该表包含订单编号、地区、销售人员和销售金额四个字段,原始数据如下:
| id | region | salesperson | amount |
|---|---|---|---|
| 1 | 华东 | 张三 | 1500 |
| 2 | 华东 | 李四 | 2000 |
| 3 | 华北 | 王五 | 1800 |
| 4 | 华东 | 赵六 | 1200 |
| 5 | 华北 | 钱七 | 2200 |
创建该表并插入示例数据的SQL语句如下:
CREATE TABLE sales (
id INT PRIMARY KEY,
region VARCHAR(20),
salesperson VARCHAR(20),
amount DECIMAL(10,2)
);
INSERT INTO sales (id, region, salesperson, amount) VALUES
(1, '华东', '张三', 1500),
(2, '华东', '李四', 2000),
(3, '华北', '王五', 1800),
(4, '华东', '赵六', 1200),
(5, '华北', '钱七', 2200);
现在假设需要生成一张报表:保留全部5条销售明细记录,同时在每一行后面增加一个字段,显示该行所属地区的销售总额。如果使用传统的SUM配合GROUP BY,查询结果会被压缩为每个地区一行,无法同时展示明细。此时窗口函数就能派上用场:
SELECT
id,
region,
salesperson,
amount,
SUM(amount) OVER (PARTITION BY region) AS region_total_amount
FROM sales;
执行结果如下:
| id | region | salesperson | amount | region_total_amount |
|---|---|---|---|---|
| 1 | 华东 | 张三 | 1500 | 4700 |
| 2 | 华东 | 李四 | 2000 | 4700 |
| 3 | 华北 | 王五 | 1800 | 4000 |
| 4 | 华东 | 赵六 | 1200 | 4700 |
| 5 | 华北 | 钱七 | 2200 | 4000 |
从结果中可以看到,原始5行数据全部保留,同时每一行都新增了region_total_amount列。华东地区的三行记录都显示总额4700,华北地区的两行记录都显示总额4000。这种写法在数据明细报表中非常常见,因为它既能满足审计或核对需求,又能快速查看分组汇总值。
除了SUM之外,COUNT和AVG同样可以使用窗口函数实现分组统计。例如,需要在每一行展示所在地区的销售人员数量和平均销售金额,可以这样写:
SELECT
id,
region,
salesperson,
amount,
COUNT(*) OVER (PARTITION BY region) AS region_person_count,
AVG(amount) OVER (PARTITION BY region) AS region_avg_amount
FROM sales;
该查询会在每一行上计算华东或华北分区的记录数以及平均金额。窗口函数并不会改变原始表的行顺序和行数,只是在结果集中增加了计算列。如果还需要同时查看每个地区的最大值、最小值,也可以将MAX(amount)和MIN(amount)与OVER子句组合,用法完全一致。
窗口函数与GROUP BY的差异及使用建议
窗口函数与GROUP BY都可以完成分组统计,但二者在结果形态和适用场景上有明显区别。GROUP BY的目的是把数据按指定列进行聚合,结果集的行数等于分组数,原始明细行会消失。如果想让GROUP BY查询同时返回明细字段,必须把所有明细字段都加入GROUP BY子句,或者使用子查询先聚合再关联,但这样会增加SQL复杂度和执行成本。窗口函数则直接作用于每一行,不会改变源表的行数,所有明细字段天然保留,同时还能附加分组统计值。
- GROUP BY:合并分组行,结果行数减少,适合只需要分组汇总值的聚合报表。
- 窗口函数:保留所有原始行,适合明细与汇总值同时展示的报表、排名、累计等分析场景。
在使用窗口函数时,有几点需要特别注意。PARTITION BY在OVER子句中是可选的。如果省略,窗口函数会把整张表视为一个分区,从而计算全局统计值,例如全表总销售额。此时查询结果中每一行都会出现同一个全局汇总值。如果需要按多个字段分组,也可以在PARTITION BY后面列出多个字段,用逗号分隔,和GROUP BY多字段分组的逻辑类似。
另一个容易忽略的细节是窗口帧的定义。对于聚合窗口函数,如果不指定ORDER BY和窗口帧,默认窗口帧为RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,也就是整个分区的所有行。这意味着无论当前行是哪一行,计算范围都包含分区内的全部记录。但如果在OVER子句中添加ORDER BY,并且不显式指定窗口帧,某些数据库的默认行为会改为从分区起始行到当前行,具体行为可能因数据库产品而异。因此,在需要明确的累计或移动统计时,建议显式写出窗口帧,例如:
-- 按地区分组,按销售金额升序计算累计销售额
SELECT
id,
region,
salesperson,
amount,
SUM(amount) OVER (
PARTITION BY region
ORDER BY amount
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS region_cumulative_amount
FROM sales;
这个查询会先按region分区,再按amount升序排列,每行的累计值从分区内金额最小的行一直累加到当前行。通过调整窗口帧,还可以实现移动平均、滚动求和等更灵活的分析功能。
综上所述,窗口函数是SQL中非常强大的分析工具,能够在保留原始数据行数的前提下完成分组统计。掌握OVER子句、PARTITION BY以及窗口帧的配合使用,可以显著提升复杂报表的编写效率和可读性。在实际工作中,如果需要同时查看明细数据与分组汇总值,应当优先考虑使用窗口函数替代传统的GROUP BY加子查询关联方案。
SQL窗口函数分组统计over_clause修改时间:2026-07-20 07:09:11