加权平均值是各值乘以其对应权重后求和再除以权重总和,即SUM(valueweight)/SUM(weight);不能直接用AVG()因其仅计算算术平均、不支持权重参数,且AVG(valueweight)错误地对加权值取平均而非实现加权逻辑。

什么是加权平均值,为什么不能直接用 AVG()?
AVG() 算的是算术平均,对每个值一视同仁。但加权平均要求每个值乘以对应权重再求和,最后除以权重总和:SUM(value <em> weight) / SUM(weight)</em>。
窗口函数本身不提供“加权平均”内置函数,必须手动组合聚合逻辑。
常见错误是误用 AVG(value weight) —— 这算的是加权值的平均,不是加权平均。
用 SUM() 和 OVER() 实现组内加权平均
核心思路:在窗口内分别计算分子(加权和)和分母(权重和),再相除。
注意必须用 SUM() 而非 AVG(),且两个 SUM() 必须使用完全相同的 PARTITION BY 子句,否则结果错位。
- 确保
weight列非 NULL,否则SUM()会跳过整行(NULL 参与乘法得 NULL,被忽略) - 若存在
weight = 0,不影响分母计算,但对应value * weight为 0,合理计入分子 - 示例(PostgreSQL/SQL Server/BigQuery 均适用):
SELECT category, value, weight, SUM(value * weight) OVER (PARTITION BY category) / SUM(weight) OVER (PARTITION BY category) AS weighted_avg FROM items;
MySQL 8.0+ 中需要额外处理除零风险
MySQL 的窗口函数支持完整,但 SUM(weight) OVER (...) 可能为 0(比如某组所有 weight 都是 0),直接相除会报错 Division by zero。
必须显式判断:
- 用
NULLIF(SUM(weight) OVER (...), 0)把分母为 0 变成 NULL,避免报错 - 或用
CASE WHEN SUM(weight) OVER (...) = 0 THEN NULL ELSE ... END - 示例:
SELECT category, value, weight, SUM(value * weight) OVER (PARTITION BY category) / NULLIF(SUM(weight) OVER (PARTITION BY category), 0) AS weighted_avg FROM items;
性能与数据类型陷阱
窗口函数会在内存中维护每个分区的聚合状态,如果 PARTITION BY 组太多或每组数据极大,可能触发内存溢出(尤其在 Spark SQL 或旧版 Presto)。
更隐蔽的问题是类型隐式转换:
- 如果
value是INT、weight是INT,SUM(value * weight)可能溢出(如 32 位整型) - 除法结果默认继承左操作数类型,
INT / INT → INT,会截断小数 - 解决方法:显式转成
DECIMAL或FLOAT,例如SUM(CAST(value AS DECIMAL(15,4)) * weight)
权重列是否归一化、是否含负数、是否代表频次而非比例——这些业务含义不会影响 SQL 计算逻辑,但会彻底改变结果解释方式。写之前务必确认字段语义。

















