用sum()窗口函数计算占比最稳妥,因能跨分组获取总量而不破坏分组逻辑;需确保分子分母过滤范围一致,用nullif防除零,乘100.0避免整数截断,并round控制小数位。

用 SUM() 窗口函数直接算占比最稳
直接在 GROUP BY 查询里除以总量,容易因分组后丢失原始行数而出错。窗口函数能跨分组访问总和,不破坏当前分组逻辑。
常见错误是写成 COUNT(*) / COUNT(*) OVER() 却忘了加 WHERE 过滤条件——如果查询带了过滤,窗口函数默认也受其影响,但很多人误以为它总算全表。
- 确保
COUNT(*)和COUNT(*) OVER()的过滤范围一致:要么都加相同WHERE,要么都不加 - 用
SUM(CASE WHEN ... THEN 1 ELSE 0 END) OVER()替代COUNT(*) OVER()更可控,尤其当需要按条件统计总量时 - 结果是小数,记得乘
100.0(不是100),避免整数除法截断(如 PostgreSQL、SQL Server)
MySQL 8.0+ 和 PostgreSQL 要用 ROUND() 控制小数位
不显式四舍五入,百分比可能显示为 23.4567890123...,前端或报表常要求保留 2 位小数。但 ROUND(x, 2) 在不同数据库行为略有差异:MySQL 默认“四舍六入五成双”,PostgreSQL 是标准四舍五入;SQLite 不支持小数位参数,得用 CAST(x AS REAL) 配合运算。
- 推荐写法:
ROUND(100.0 * COUNT(*) / COUNT(*) OVER(), 2) - 如果字段本身是
DECIMAL,先转DOUBLE PRECISION或乘100.0再 round,否则某些引擎(如旧版 MySQL)会隐式截断 - 别用
FORMAT()(MySQL)或TO_CHAR()(PostgreSQL)做计算——它们返回字符串,无法参与后续数值运算
SQLite 没窗口函数?用子查询硬套
SQLite 3.25 以前不支持窗口函数,COUNT(*) OVER() 直接报错。这时只能把总量查出来塞进子查询,虽然性能差一点,但兼容性好。
- 写法示例:
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM table_name WHERE condition) - 注意子查询里的
WHERE必须和外层完全一致,否则占比失真;可提取为 CTE 复用,但 SQLite 3.8.3+ 才支持 - 如果外层有
HAVING,子查询不能复用——因为HAVING是分组后过滤,总量必须在外层分组前算
分母为零时返回 NULL 而不是报错
空表、全被 WHERE 过滤掉、或分组后某组数据为空时,分母可能为 0。多数数据库(PostgreSQL、SQL Server)会抛 division by zero 错,MySQL 默认警告并返回 NULL,但行为可配置。
- 安全写法:用
NULLIF(denominator, 0)把 0 变成NULL,再让分子除它——结果自动为NULL - 示例:
ROUND(100.0 * COUNT(*) / NULLIF(COUNT(*) OVER(), 0), 2) - 不要用
CASE WHEN denominator = 0 THEN 0 ELSE ... END——逻辑冗余,且易漏掉NULL分母场景
分母是否为零,往往取决于业务过滤条件是否动态变化,上线前最好用空数据集验证一次。











