销售贡献度是某维度下某项销售额占该维度总销售额的比例;不能直接用group by,因会丢失明细层级,需用窗口函数sum() over(partition by...)在保留行粒度前提下计算分组总计。

什么是销售贡献度,为什么不能直接用 GROUP BY
销售贡献度本质是「某个维度下某项销售额占该维度总销售额的比例」,比如华东区某门店销售额占华东区总销售额的百分比。如果用 GROUP BY 先聚合再除,会丢失明细层级——你没法在保留每条订单记录的同时,又知道它所属区域的总销售额。窗口函数正是解决这个问题的核心:它允许你在不破坏原始行粒度的前提下,跨行计算聚合值。
SUM() OVER (PARTITION BY ...) 是最常用的基础写法
计算单维度贡献度(如按区域)时,SUM(sales_amt) OVER (PARTITION BY region) 会为每一行补上其所在 region 的总销售额;再用本行销售额除以该值即可:
SELECT order_id, region, sales_amt, ROUND(sales_amt / SUM(sales_amt) OVER (PARTITION BY region), 4) AS region_contribution FROM sales;
- 注意
SUM() OVER必须和当前行处于同一逻辑分组,否则结果无意义 - 如果
region有 NULL 值,默认会被归为同一组,需提前用WHERE region IS NOT NULL过滤或用COALESCE(region, 'unknown') - 除零风险:当某
region所有sales_amt都为 0,结果会是NULL或报错(取决于数据库),建议加NULLIF(SUM(...), 0)
多维度嵌套时,PARTITION BY 支持复合字段,但顺序不重要
要算「每个区域中每个品类的销售占比」,直接写 PARTITION BY region, category 即可,不需要先按区域聚合再按品类分组:
SELECT region, category, sales_amt, sales_amt / SUM(sales_amt) OVER (PARTITION BY region, category) AS cat_in_region_ratio FROM sales;
- 复合
PARTITION BY等价于先按第一个字段分组,再在组内按第二个字段分组,和GROUP BY region, category的语义一致 - 但窗口函数不会压缩行数,所以你能同时看到「华东-手机」的单笔订单、该品类在华东的总额、以及华东所有品类总额(后者需额外加一层
PARTITION BY region) - PostgreSQL 和 SQL Server 支持
ORDER BY+ROWS BETWEEN做滑动窗口,但贡献度计算一般不需要排序,加了反而易出错
容易被忽略的 NULL 和精度问题
真实业务数据里,sales_amt 为 NULL 很常见,而 SUM() OVER 默认忽略 NULL,但除法遇到 NULL 会整行变 NULL。更麻烦的是浮点精度——比如 12.3456% 显示成 0.12344999999999999。
- 统一用
COALESCE(sales_amt, 0)把 NULL 转 0,避免传播 - 用
ROUND(..., 4)控制小数位,别依赖前端格式化,因为不同数据库对ROUND(0.12345, 4)的处理可能不同(如 MySQL 截断,PostgreSQL 四舍五入) - 如果要做累计贡献度(如 TOP 5 品类占区域多少),得先用
ROW_NUMBER() OVER (PARTITION BY region ORDER BY sales_amt DESC)排序,再套窗口求和——这里排序字段和聚合字段必须严格一致,否则排名和求和对不上
真正难的不是写对一行窗口函数,而是确认你的 PARTITION BY 字段是否覆盖了业务要求的“维度边界”,以及 NULL 和零值在上下游系统中如何解释。











