因为AVG()仅做等权平均,忽略权重字段;加权平均需用SUM(值×权重)/SUM(权重),并过滤weight≤0及处理NULL和精度问题。

为什么直接用 AVG() 算不出加权平均数
因为 AVG() 是对值做等权平均,它会把每行当做一个独立样本,完全忽略权重字段的存在。比如你有一组商品销量(值)和对应门店规模(权重),想算“按门店规模加权的平均销量”,AVG(sales) 只是把所有门店销量简单相加再除以门店数,结果严重偏离业务含义。
真正需要的是:每个 sales 先乘以其对应的 weight,求和后除以总权重 —— 即 SUM(sales * weight) / SUM(weight)。这个公式在分组场景下依然成立,但要注意 NULL 和 0 权重的干扰。
分组加权平均的正确写法(含防错处理)
核心就是把加权公式套进 GROUP BY 查询里,但必须主动处理两类异常:
-
weight为NULL或0的行会导致除零错误或结果失真,要用WHERE weight > 0过滤 -
sales为NULL时,sales * weight也会是NULL,被SUM()自动忽略 —— 这通常是期望行为,但需确认业务逻辑是否允许缺失值参与计算
示例(按部门计算加权平均绩效分):
SELECT dept, SUM(score * weight) / SUM(weight) AS weighted_avg_score FROM performance WHERE weight > 0 AND score IS NOT NULL GROUP BY dept;
遇到 DECIMAL 精度丢失怎么办
如果 score 或 weight 是整型,而结果出现截断(如只保留整数位),大概率是数据库隐式类型转换导致精度丢失。例如 PostgreSQL 中两个 INT 相除得整数,MySQL 默认也倾向整数除法。
解决方法统一:显式转成高精度数值类型:
- PostgreSQL / SQL Server:
CAST(SUM(score * weight) AS DECIMAL(10,4)) / CAST(SUM(weight) AS DECIMAL(10,4)) - MySQL:
SUM(score * weight) / SUM(weight + 0.0)(加 0.0 强制浮点运算) - BigQuery:
SAFE_DIVIDE(SUM(score * weight), SUM(weight))(自动处理除零)
能否用窗口函数替代 GROUP BY 做加权平均
可以,但目的不同:窗口函数不压缩行数,适合在原表每行上“附带”本组的加权均值,而不是聚合出新行。语法结构类似,只是把 GROUP BY 换成 PARTITION BY:
SELECT dept, name, score, weight, SUM(score * weight) OVER (PARTITION BY dept) / SUM(weight) OVER (PARTITION BY dept) AS dept_weighted_avg FROM performance WHERE weight > 0;
注意:这里不能用 WHERE 过滤后再开窗,否则窗口计算范围会变小;更稳妥的做法是把过滤逻辑放进条件聚合,或先用 CTE 预过滤。
实际用哪个,取决于你要的是「汇总报表」还是「明细增强」——前者用 GROUP BY,后者用窗口函数。别为了省一行代码混用,容易漏掉权重归一化逻辑。










