最安全的加权平均sql写法是在子查询中返回value×weight和weight两列,外层用coalesce(sum(vw)1.0/nullif(sum(w),0),0),并确保where过滤下推、精度显式控制。

子查询里直接用 SUM(a * b) / SUM(b) 最安全
加权平均值本质就是 ∑(权重 × 值) ÷ ∑(权重),SQL 里最直白、最不易出错的写法就是在子查询中一次性算出分子和分母。别想着先查出带权重的明细再在外面套一层 AVG()——AVG() 对权重无感,会当成等权处理。
常见错误是这样写:
SELECT AVG(weighted_value) FROM (SELECT value * weight AS weighted_value FROM t) t2
这完全错了:它算的是加权值的算术平均,不是加权平均。
正确做法是:
- 子查询必须返回两列:一列是
value * weight(用于求和),一列是weight(也用于求和) - 外层用
SUM(value * weight) / SUM(weight),注意加括号防空值干扰 - 强烈建议加上
WHERE weight IS NOT NULL AND weight > 0,避免除零或 NULL 传播
COALESCE 和 NULLIF 必须配合使用
一旦子查询结果为空,或所有 weight 为 0 / NULL,SUM(weight) 返回 NULL,导致整个除法结果为 NULL——这不是你想要的“0”或报错提示。
所以实际生产 SQL 要兜底:
SELECT COALESCE(SUM(v * w) * 1.0 / NULLIF(SUM(w), 0), 0) AS weighted_avg FROM (SELECT value AS v, weight AS w FROM sales WHERE region = 'north') t
这里关键点:
-
NULLIF(SUM(w), 0)把分母为 0 的情况转成NULL,防止除零错误 -
* 1.0强制转为浮点,避免整数除法截断(比如SUM(v*w)=5,SUM(w)=3,不转就是1) -
COALESCE(..., 0)把最终NULL(空数据集、全零权重等)统一转成 0;按需可换成NULL或抛异常逻辑
WHERE 条件必须下推到子查询内部
如果把过滤条件放在外层,比如:
SELECT SUM(v*w)/SUM(w) FROM (SELECT value v, weight w FROM sales) t WHERE v > 100
会导致 SUM(w) 只对 v > 100 的行求和,但分母本应是全部参与加权的权重总和——语义已歪。加权平均的分母永远是“所选样本的权重总和”,不是“全表权重总和”。
正确方式是:
- 所有业务过滤(时间范围、状态、分类等)都写在子查询的
WHERE里 - 子查询输出的就是你要加权计算的那个数据集,不多不少
- 尤其注意关联子查询场景:若子查询含
JOIN,确保weight和value来自同一逻辑行,避免笛卡尔放大
PostgreSQL/MySQL/SQL Server 在 DECIMAL 精度上表现不同
加权平均常用于财务、评分等精度敏感场景。不同数据库对 SUM(DECIMAL) / SUM(DECIMAL) 的默认精度处理不一致:
- PostgreSQL 默认保留源字段小数位,但除法可能降精度,建议显式
ROUND(..., 4) - SQL Server 中
DECIMAL(p,s) / DECIMAL(p,s)结果的精度和小数位有复杂推导规则,常意外截断;推荐先转FLOAT再转回DECIMAL - MySQL 8.0+ 对
DIVIDE函数支持有限,仍建议用/配合CAST(... AS DECIMAL(10,4))
最稳的跨库写法是:子查询里就用 CAST(value AS DECIMAL(15,6)) 和 CAST(weight AS DECIMAL(15,6)),外层再 ROUND(..., 4) 输出。
权重为负数、小数位超长、或存在极小权重(如 1e-8)时,浮点误差会明显,这种时候别省事,老老实实用定点数 + 显式精度控制。










