在数据分析的实际工作中,计算分组内的百分比占比是非常常见的需求,比如统计不同产品类别的销量占全品类总销量的比例、不同地区的用户数占全平台用户数的比例等。这类需求如果用传统的SQL写法,往往需要先统计分组总和,再统计总体总和,最后通过子查询或者表关联完成计算,步骤繁琐且性能不佳。窗口函数的出现让这类计算变得简单高效,它可以在保留原表所有行的基础上,对指定窗口内的数据进行聚合计算,非常适合处理分组占比类的需求。

窗口函数基础概念
窗口函数也叫OLAP函数,它会对一组相关行执行计算,同时不会将多行合并成单行,这和普通的聚合函数有本质区别。普通聚合函数比如SUM()、COUNT()会把分组内的多行数据合并成一行结果,而窗口函数会在每一行都返回一个计算结果,原表的行数不会发生变化。
窗口函数的基本语法结构如下:
函数名(参数) OVER (
[PARTITION BY 分组列]
[ORDER BY 排序列 [ASC|DESC]]
[窗口帧定义]
)
其中PARTITION BY用来指定分组的列,作用类似GROUP BY,但不会合并行;ORDER BY用来指定窗口内数据的排序规则;窗口帧定义可以指定计算的范围,不过计算分组占比时通常不需要额外设置。
计算分组内百分比占比的核心思路
要计算分组内的百分比占比,核心逻辑是:先通过窗口函数计算出每个分组的总和,再计算出所有数据的总和,最后用分组总和除以所有数据的总和得到占比。具体步骤如下:
- 第一步:用
SUM(目标列) OVER(PARTITION BY 分组列)计算每个分组的总和,得到分组维度的聚合值 - 第二步:用
SUM(目标列) OVER()计算所有数据的总和,这里没有PARTITION BY,窗口就是整个数据集 - 第三步:用分组总和除以总体总和,再乘以100得到百分比,最后可以根据需要保留小数位数
实际案例演示
假设我们有一张销售数据表sales_data,表结构如下:
| 列名 | 类型 | 说明 |
|---|---|---|
| department | VARCHAR | 部门名称 |
| product | VARCHAR | 产品名称 |
| sales_amount | INT | 销售额 |
现在需要统计每个部门的销售额占全公司总销售额的百分比,具体实现SQL如下:
SELECT
department,
SUM(sales_amount) AS dept_total_sales, -- 部门总销售额
SUM(sales_amount) OVER() AS company_total_sales, -- 全公司总销售额
-- 计算占比,保留2位小数,乘以100得到百分比
ROUND(SUM(sales_amount) * 100.0 / SUM(sales_amount) OVER(), 2) AS sales_percent
FROM sales_data
GROUP BY department;
上面的查询中,SUM(sales_amount) OVER()没有指定PARTITION BY,所以窗口是整个表,计算的是全公司的总销售额。然后先按部门分组计算每个部门的销售额总和,再用部门总和除以公司总和得到占比。
如果还需要同时展示每个产品的销售额占部门销售额的百分比,不需要额外分组,直接用窗口函数即可:
SELECT
department,
product,
sales_amount,
-- 产品销售额占部门销售额的百分比
ROUND(sales_amount * 100.0 / SUM(sales_amount) OVER(PARTITION BY department), 2) AS dept_percent
FROM sales_data;
这里SUM(sales_amount) OVER(PARTITION BY department)会计算每个部门内的销售额总和,然后用单条产品的销售额除以部门总和,就得到了该产品在部门内的销售占比,原表的所有行都会保留,不需要GROUP BY。
注意事项
在使用窗口函数计算百分比时,需要注意几个问题:
- 除法计算时要保证分子或者分母至少有一个是浮点型,否则整数除法会得到整数结果,比如上面的
100.0就是为了保证计算结果是浮点型,得到准确的百分比小数 - 如果数据中存在
NULL值,SUM函数会自动忽略NULL,如果需要把NULL当作0处理,可以用COALESCE(列名, 0)包裹目标列 - 不同数据库对窗口函数的支持略有差异,主流的MySQL 8.0+、PostgreSQL、SQL Server、Oracle都支持标准窗口函数语法,低版本数据库可能无法使用
窗口函数除了计算分组占比,还可以实现排名、前后行取值、移动平均等多种分析功能,是SQL进阶学习中非常重要的内容,掌握后可以大幅提升复杂查询的编写效率。