组内占比怎么算
占比就是当前行的值除以所属分组(或全表)的合计。用窗口函数一步算出:SUM(amount) OVER (PARTITION BY 维度) 提供分母,与当前行的 amount 相除即可,不需要再写子查询或自连接去对齐分母。分母若是整表合计,直接去掉 PARTITION BY;若是上一层级(例如全国下的省份),要么用两级分区,要么先算出层级合计再取。除零和空值必须显式保护,否则 PostgreSQL 直接报错、MySQL 静默返回 NULL。
基本信息
| 适用平台 | MySQL 8.0+ / PostgreSQL 11+(窗口函数与嵌套聚合) |
|---|---|
| 组内分母 | SUM(x) OVER (PARTITION BY 维度) |
| 全表分母 | SUM(x) OVER (),不写 PARTITION BY |
| 除零保护 | 用 NULLIF(分母, 0),PostgreSQL 除零报错,MySQL 返回 NULL |
| 类型细节 | PostgreSQL 整数相除会截断成整数,需先转 numeric;MySQL 的 / 返回十进制 |
典型使用场景
- 各渠道销售额占总量的比例,用于结构分析
- 各城市订单量占全国比例,直接按占比排序
- 部门费用占公司总费用的份额
- 单笔订单占该客户累计消费的比例
- 与排名配合做 Pareto 分析(前 20% 客户贡献多少营收)
聚合行上的占比
聚合行上的占比
SELECT channel,
SUM(amount) AS amount,
ROUND(100 * SUM(amount) / SUM(SUM(amount)) OVER (), 2) AS pct_of_total
FROM orders
WHERE created_at >= '2026-01-01'
GROUP BY channel
ORDER BY pct_of_total DESC;
# 内层 SUM 完成分组聚合,外层窗口函数在全部分组上再求和明细行上的占比
明细行上的占比
# 分母是该客户的全部消费,按明细行给出份额
SELECT o.id, o.user_id, o.amount,
SUM(o.amount) OVER (PARTITION BY o.user_id) AS user_total,
ROUND(100 * o.amount
/ NULLIF(SUM(o.amount) OVER (PARTITION BY o.user_id), 0), 2) AS pct_in_user
FROM orders o
WHERE o.status = 'paid'
ORDER BY o.user_id, o.id;分母口径怎么选
| 目标 | 分母写法 | 注意点 |
|---|---|---|
| 占全表比例 | SUM(x) OVER () | 不写 PARTITION BY |
| 占本组比例 | SUM(x) OVER (PARTITION BY 维度) | 明细行与聚合行都可用 |
| 占上一层级比例 | 两级分组或先用 CTE 算层级合计 | 先确定层级口径再写分母 |
| 占比总和校验 | 各组占比相加应接近 100% | 有排除条件或 NULL 时会对不上,属正常 |
嵌套聚合 SUM(SUM(x)) OVER () 在 MySQL 8 与 PostgreSQL 上都可用:内层完成分组,外层在全部分组上求和,比再写一个子查询求总量更简洁,也不会有对齐风险。
类型引发的精度问题必须确认:PostgreSQL 里整数相除会截断(3/4 得 0),要先转 numeric;除零在 PG 直接报错、在 MySQL 返回 NULL,两种都要用 NULLIF 显式保护,跨库脚本尤其要注意。