PostgreSQL中需手动计算加权平均:分组场景用GROUP BY配合SUM(score*weight)/NULLIF(SUM(weight),0),窗口场景用SUM() OVER(PARTITION BY...),注意类型转换、NULL处理及权重归一化。

用 AVG() 直接算加权平均?不行,得自己写
PostgreSQL 没有内置的 weighted_avg() 聚合函数,AVG() 只对值等权处理。想按权重算分组加权平均,必须手动组合:分子是 sum(value * weight),分母是 sum(weight)。窗口函数不是必须的——只有当你需要同时保留原始行、又在每组内算加权均值时,才用 OVER (PARTITION BY ...)。
分组加权平均的标准写法:用 GROUP BY + 手动公式
这是最常见也最稳妥的做法,适用于报表汇总、聚合查询等场景:
SELECT category, SUM(score * weight) / NULLIF(SUM(weight), 0) AS weighted_avg FROM exam_results GROUP BY category;
注意几个关键点:
-
NULLIF(SUM(weight), 0)防止除零错误,比CASE WHEN SUM(weight) = 0 THEN NULL ELSE ... END更简洁 - 如果
weight列可能为NULL,先用WHERE weight IS NOT NULL过滤,或在求和前用COALESCE(weight, 0)——但后者会把空权重当 0 处理,逻辑是否合理需结合业务判断 - 结果类型由输入列决定:
score和weight若为INTEGER,除法会截断小数;建议至少一个转为NUMERIC,例如SUM(score::NUMERIC * weight) / NULLIF(SUM(weight), 0)
加窗口函数的典型场景:保留明细行并打上组内加权均值标签
比如你有一张订单明细表,想在每行显示“该客户所有订单的加权平均单价(按数量加权)”,这时就得用窗口函数:
SELECT
order_id,
customer_id,
price,
quantity,
SUM(price * quantity) OVER (PARTITION BY customer_id) /
NULLIF(SUM(quantity) OVER (PARTITION BY customer_id), 0) AS cust_weighted_avg_price
FROM orders;要点说明:
- 两个
SUM() OVER (...)必须用完全相同的PARTITION BY子句,否则分母和分子不在同一组内,结果无意义 - 不能写成
AVG(price) OVER (PARTITION BY customer_id ORDER BY ...)——那是普通平均+排序窗口,和加权无关 - 如果某客户所有
quantity都是NULL或全为 0,这一行的cust_weighted_avg_price会是NULL(因为NULLIF返回NULL),这是预期行为
性能与精度陷阱:别让隐式类型转换拖慢查询
加权平均计算本身不重,但类型不匹配会悄悄引发问题:
- 若
weight是SMALLINT、score是REAL,score * weight结果是REAL,但累加过程中可能丢失精度;金融类场景建议统一用NUMERIC(p,s) - 大表上做
SUM() OVER (PARTITION BY ...)时,如果分区键基数极高(比如千万级唯一用户 ID),窗口计算会显著变慢;此时应优先考虑预聚合(先GROUP BY再JOIN回明细) - PostgreSQL 14+ 支持
APPROX_COUNT_DISTINCT,但加权平均没有近似版本——所有需求都必须走精确计算路径
真正容易被忽略的是权重归一化:业务上是否要求权重和必须为 1?如果原始权重是百分比但存成了 0–100 的整数,漏掉除以 100 就会导致结果放大百倍——这种错误不会报错,只会在报表里静默出错。

















