占比与比率怎么算
比率等于部分除以整体,用聚合相除时有两个必须注意的点:整数除法会先截断,分母可能为 0。计算全局占比时常用 SUM(…) OVER () 取到总合计当作分母,省掉再写一条查询。
基本信息
| 基本形态 | 部分除以整体,如 COUNT(*) / SUM(COUNT(*)) OVER () |
|---|---|
| 整数除法 | MySQL 的 / 返回小数;PostgreSQL 的整数相除先截断 |
| 防除零 | 分母统一套 NULLIF(x, 0) |
| 百分比 | 乘 100.0 先把小数引入,避免先截断再放大 |
| 分组占比 | 配合 SUM(…) OVER () 拿到所有分组的合计作为分母 |
| 精度控制 | 外层用 ROUND 限定小数位 |
分组占比示例
分组占比示例
SELECT city,
COUNT(*) AS cnt,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) AS pct
FROM users
GROUP BY city
ORDER BY cnt DESC;
-- SUM(COUNT(*)) OVER () 是所有城市的用户总数,作为分母
-- 各城市占比相加约等于 100,受舍入影响可能有 0.01 级偏差PostgreSQL 与条件比率
PostgreSQL 与条件比率
-- PostgreSQL 里整数相除先截断,必须把小数提前引入
SELECT count(*) FILTER (WHERE status = 1) AS paid, -- PG 支持 FILTER 条件聚合
ROUND(100.0 * count(*) FILTER (WHERE status = 1)
/ NULLIF(count(*), 0), 2) AS paid_pct
FROM orders;要点与坑
| 要点 | 说明 | 后果 |
|---|---|---|
| 分母选择 | 整体量,常用窗口合计 | 分母选错会出现占比之和不为 100 |
| 整数除法 | PG 先截断,MySQL 返回小数 | 比率整列变成 0 |
| 除零保护 | 分母为 0 时报错或返回 NULL | 用 NULLIF 兜底 |
| 舍入 | 各分组分别舍入 | 相加可能与 100 有微小偏差 |
| NULL 传播 | 分子或分母为 NULL | 结果为 NULL,需要分类讨论 |
典型使用场景
- 各渠道、各城市占比,用于大盘分析
- 转化率:分子是转化量,分母是曝光或点击量
- 同比环比:本期与上期相比的增长比例
- 达成率:实际完成除以目标值
- 缺陷率、故障率这类质量指标
- 分桶占比:观察指标分布是否集中
- 份额分析:某商品销量占该品类总量的比例,分母用窗口合计一次算出
占比之和不必严格等于 100:各分组独立舍入后,相加可能与 100 相差 0.01 到 0.1 个百分点,报表说明里最好注明“合计可能因舍入略有偏差”。
分子分母要同源:占比的两侧必须来自同一时间窗与同一过滤条件,否则会算出大于 100% 的值。改查询时两边一起改,别只改一半。
分母为 0 时两库行为不同:MySQL 返回 NULL 并产生告警,PostgreSQL 直接报 division by zero。跨库 SQL 一律用 NULLIF(分母, 0) 包住,避免库间差异引发线上故障。