加权平均是各值乘以其权重后求和再除以权重总和,不能直接用avg()函数计算,因其忽略权重;典型场景如gpa计算需用sum(score*credit)/sum(credit),并需处理null、除零及整数除法截断问题。

什么是加权平均,为什么不能直接用 AVG()
加权平均不是所有值简单相加再除以个数,而是每个值乘以对应权重后求和,再除以权重总和。SQL 的 AVG() 函数只认数值本身,完全忽略权重字段,硬套会得出错误结果。
常见场景比如:学生成绩表里有 score 和 credit(学分),GPA 计算必须用 score × credit 累加,再除以 credit 总和;又或者销售数据中不同产品的销量要按单价加权计算“等效均价”。
用子查询实现加权平均的典型写法
核心思路是把分子(加权和)和分母(权重和)分别算出来,再相除。子查询最稳妥,尤其当主表带过滤或分组时,能避免聚合层级错乱。
- 分子用
SUM(score * credit),分母用SUM(credit),二者必须在同一粒度下计算 - 如果需要按学生分组算 GPA,外层
GROUP BY student_id,子查询得写成相关子查询或改用窗口函数,但简单场景优先用聚合 + 直接除法 - 注意
NULL值:credit为NULL会导致整行被SUM()忽略,但若score为NULL而credit非空,score * credit结果为NULL,也会丢弃——必要时加WHERE credit IS NOT NULL AND score IS NOT NULL
示例(学生课程表 enrollments):
SELECT SUM(score * credit) * 1.0 / SUM(credit) AS weighted_avg_gpa FROM enrollments WHERE semester = '2024F';
嵌套子查询 vs. 直接聚合,什么时候必须嵌套?
多数情况下,一行聚合表达式就够了;只有在需要“先筛选再加权”,且筛选逻辑依赖加权结果时,才真正需要嵌套子查询。
- 比如:只保留加权贡献 > 5 的课程,再算整体加权平均——这时得先在子查询里算出每门课的
score * credit,外层再过滤、再汇总 - 另一个典型是跨表加权:主表无权重字段,需从关联表查出权重,此时常写成
(SELECT weight FROM weights w WHERE w.id = t.id)作为表达式的一部分,本质也是嵌套 - 性能上,相关子查询可能变慢,尤其是大表;能用
JOIN+ 聚合替代的,优先JOIN
NULL 和除零问题,最容易被跳过的两个坑
加权平均的分母是权重和,不是记录数,所以 SUM(credit) 完全可能为 0(比如所有 credit 都是 0 或被 WHERE 过滤光了),直接除会报错或返回 NULL。
- 用
CASE WHEN SUM(credit) = 0 THEN NULL ELSE SUM(score * credit) / SUM(credit) END显式兜底 - PostgreSQL 可用
NULLIF(SUM(credit), 0)配合COALESCE();MySQL 用NULLIF()同样有效 - 另外,
* 1.0强转浮点很关键——否则整数除法会截断小数(如7/2 = 3),务必检查字段类型和数据库默认除法规则
权重本身是负数?业务上通常不允许,但 SQL 不拦着,得靠约束或 WHERE credit > 0 主动防御。











