avg(condition)可直接计算比例,因sql标准将true/false隐式转为1/0;不支持的数据库需用sum(case when then 1 else 0 end)1.0/nullif(count(),0),并注意整数除法与null处理。

用 COUNT 和 SUM 配合条件表达式算比例
直接用 AVG 最省事,但很多人不知道它在布尔表达式里自动转 0/1——AVG(condition) 就是满足条件的比例。如果数据库不支持布尔转数值(比如旧版 MySQL),就得手动转:SUM(CASE WHEN condition THEN 1 ELSE 0 END) * 1.0 / COUNT(*)。注意乘 1.0 是为了避免整数除法截断(如 PostgreSQL 默认保留小数,但 SQLite、MySQL 5.x 可能返回 0)。
-
AVG(status = 'success')在 MySQL 8+/PostgreSQL 中有效;在 SQLite 中也行,但在 MySQL 5.7 及更早版本中会报错或返回意外结果 - 用
CASE更兼容:例如统计每部门中薪资超 10000 的员工占比:SELECT dept, SUM(CASE WHEN salary > 10000 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS ratio FROM emp GROUP BY dept;
- 如果分组字段可能为
NULL,COUNT(*)仍计行数,但COUNT(col)会忽略NULL,别混用
处理分母为零的场景
当某组无数据(比如某个 category 在当前查询中没出现),GROUP BY 不会产生该组;但如果用了 LEFT JOIN 或预定义维度表,就可能出现 COUNT(*) = 0,导致除零错误。不同数据库行为不同:PostgreSQL 报错,MySQL 返回 NULL,SQLite 返回 0。
- 安全写法统一用
NULLIF:把分母变成NULL后,整个除法结果也是NULL,不会崩SUM(CASE WHEN flag THEN 1 ELSE 0 END) * 1.0 / NULLIF(COUNT(*), 0)
- 想显示
0而非NULL,套一层COALESCE(..., 0) - 别用
WHERE过滤掉空组后再算——那样你根本看不到那些组,比例统计就缺维了
百分比格式化与精度控制
原始比例是小数(如 0.723),要展示成 “72.3%” 得靠四舍五入和拼接。各数据库函数名不同,但逻辑一致:先缩放、再取整、最后加符号。
- 常见写法:
ROUND(AVG(flag) * 100, 1) || '%' -- PostgreSQL/SQLite
CONCAT(ROUND(AVG(flag) * 100, 1), '%') -- MySQL
-
ROUND(x, 1)是保留一位小数,不是“四舍五入到整数”;误写成ROUND(x * 100)会丢精度 - 如果业务要求“向上取整”(比如合规场景说“至少 70%”),得用
CEIL,不能只靠ROUND
窗口函数里算组内比例(不带 GROUP BY)
有时不想聚合行,而是要在原表每行上标出“它所属分组里满足条件的占比”,就得用窗口函数。核心是把聚合函数改成带 OVER(PARTITION BY ...) 的形式。
- 示例:给每个订单打标,显示“该用户所有订单中已发货比例”
SELECT user_id, status, AVG(status = 'shipped') OVER (PARTITION BY user_id) AS shipped_ratio FROM orders; - 注意:窗口版
AVG不能直接跟CASE嵌套再OVER,必须把条件表达式放在AVG()里面(或用SUM(...) / COUNT(...) OVER) - 性能上,窗口计算比
GROUP BY+JOIN回原表通常更快,尤其数据量大时
实际写的时候,先确认数据库版本对布尔表达式的支持程度,再决定用 AVG(condition) 还是兜底的 CASE 写法;比例值是否参与后续计算(比如排序、筛选)也会影响要不要提前 ROUND ——中间计算尽量保留原始精度,展示层再格式化。











