avg(case when ...) 是最简练、兼容性最好的分组内条件比例写法,因布尔表达式在数值上下文中自动转为1或0,avg即满足条件行数除以非null判断行数,mysql 5.7需case/if,postgresql需::int显转,且须注意null是否计入分母。

AVG(CASE WHEN ...) 是最简练、兼容性最好的分组内条件比例写法,但必须写 1.0 和 0.0,否则在多数数据库里结果会是整数 0 或 1。
为什么 AVG(CASE WHEN) 能直接算比例
因为 SQL 标准规定:布尔表达式在数值上下文中自动转为 1(TRUE)或 0(FALSE),而 AVG() 本质就是求平均值——即“满足条件的行数 ÷ 非 NULL 的判断结果行数”。
这个逻辑在 MySQL 8.0+、PostgreSQL、SQL Server、Oracle、SQLite 均成立,但细节有差异:
- MySQL 5.7 及更早版本不支持布尔值隐式转数字,必须用
CASE WHEN或IF() - PostgreSQL 对
boolean类型直接调AVG()会报错:function avg(boolean) does not exist,必须先转::int - 所有版本都默认忽略
NULL输入项,所以salary IS NULL时,salary > 10000返回NULL,该行既不进分子也不进分母
MySQL 中 IF() 比 CASE WHEN 更省事,但别漏小数点
在 MySQL 里,IF(salary > 10000, 1.0, 0.0) 比 CASE WHEN ... THEN 1.0 ELSE 0.0 END 少打一半字,语义也更直白。关键陷阱是:
-
AVG(IF(salary > 10000, 1, 0))→ 错!整数运算,结果被截断为0或1 -
AVG(IF(salary > 10000, 1.0, 0.0))→ 正确,触发浮点计算 -
ROUND(AVG(IF(salary > 10000, 1.0, 0.0)), 3)→ 推荐,控制精度,避免尾部浮点误差
PostgreSQL 必须显式转换 boolean 到 int,括号位置不能错
PostgreSQL 不允许对布尔值直接调 AVG(),必须用类型转换。正确写法是:
SELECT dept, ROUND(AVG((salary > 10000)::int), 3) AS high_salary_ratio FROM employees GROUP BY dept;
注意:(salary > 10000)::int 是先判断再转整;如果写成 salary > 10000::int,就变成拿 salary 和整数 10000 比较,语义全错。另外,::int 会把 TRUE → 1、FALSE → 0、NULL → NULL,和 CASE 行为一致。
NULL 值是否计入分母,取决于你是否想把它当 FALSE
AVG() 默认跳过 NULL,所以如果字段本身含 NULL(比如 salary 为空),那 salary > 10000 的结果就是 NULL,该行不参与计算——分母变小了。如果你希望 NULL 算作“不满足条件”,就得显式覆盖:
AVG(CASE WHEN salary > 10000 THEN 1.0
WHEN salary IS NULL THEN 0.0
ELSE 0.0 END)
这种写法确保每行都贡献一个 1.0 或 0.0,分母等于 COUNT(*),更贴近业务中“应统计全部员工”的预期。










