分组加权平均 = sum(值 * 权重) / nullif(sum(权重), 0),需在 group by 下按组分别计算分子分母;权重为null时sum自动忽略,应显式过滤或处理;避免重复sum影响性能。

用 SUM() 和除法算分组加权平均,核心是别漏乘权重
直接说结论:分组加权平均 = SUM(值 * 权重) / SUM(权重),必须在 GROUP BY 下对每组分别算这两个和。常见错误是只写 AVG(值 * 权重) 或忘了分母也要 SUM(),结果完全不对。
比如计算每个部门的「平均绩效得分」,但得分要按员工职级(初级/中级/高级)加权——这时不能用 AVG(score),也不能写成 AVG(score * weight),因为后者会把加权后的值再取算术平均,丢失原始权重比例。
-
SUM(score * weight)是总加权分,必须用这个做分子 - 分母必须是
SUM(weight),不是COUNT(*)或SUM(1),否则权重归一化就失效 - 如果某组所有
weight为 0 或 NULL,SUM(weight)为 0,除零会报错或返回NULL,得加NULLIF防御
防除零:用 NULLIF() 包住分母
数据库遇到除零不会自动跳过,MySQL 返回 NULL(但行为不一致),PostgreSQL 直接报错 division by zero,SQL Server 报 Msg 8134。所以分母必须显式处理。
正确写法是:SUM(score * weight) / NULLIF(SUM(weight), 0)。这样当分母为 0 时整条表达式返回 NULL,不影响其他组计算。
- 别用
CASE WHEN SUM(weight) = 0 THEN NULL ELSE ... END,多写三行还容易漏ELSE -
NULLIF(a, b)是标准 SQL 函数,所有主流数据库都支持,比自定义逻辑更可靠 - 如果业务上要求返回 0 而非
NULL,再套一层COALESCE(..., 0),但注意 0 可能掩盖数据异常
权重含 NULL 时,SUM() 自动忽略,但你要知道它被忽略了
SUM() 遇到 NULL 值默认跳过,不报错也不警告。这看起来省心,但容易埋坑:比如某员工的 weight 是 NULL,那他的 score 就彻底不参与计算——既没进分子,也没进分母。
这不一定是错,但得确认是否符合业务预期。例如职级未填写的员工,该不该计入部门加权平均?
- 想排除
NULL权重是合理设计,但要在 WHERE 中显式过滤:WHERE weight IS NOT NULL - 如果想把
NULL当 0 处理,得写COALESCE(weight, 0),但注意这会让分母变大,拉低整体加权值 - 检查数据质量:执行
SELECT COUNT(*) FROM t WHERE weight IS NULL,心里有数再决定怎么处理
性能注意:别在 SELECT 里重复写 SUM()
有人图省事写两遍 SUM():SUM(score * weight) / SUM(weight) 看似简洁,但某些数据库(如旧版 MySQL)可能对同一聚合表达式重复计算,尤其数据量大、权重字段是表达式时(比如 SQRT(sales))。
更稳妥的方式是用子查询或 CTE 提前算好分子分母,避免潜在重复开销:
SELECT dept, total_weighted_score / NULLIF(total_weight, 0) AS weighted_avg
FROM (
SELECT dept,
SUM(score * weight) AS total_weighted_score,
SUM(weight) AS total_weight
FROM employees
GROUP BY dept
) t;
CTE 更易读,但子查询在老版本兼容性更好。重点不是语法炫技,而是让数据库一次算清,别让它猜你是不是真需要两次 SUM。
真正难的不是写对公式,是搞清楚权重代表什么、谁该被包含、谁该被排除——这些没法靠函数自动判断,得翻需求文档,或者直接问业务方。










