加权平均价格的正确sql写法是sum(price * quantity) / sum(quantity),需配合group by处理非聚合字段,并用case when防除零错误。

加权平均价格的正确SQL写法:别直接用AVG()
加权平均价格不是把单价取个平均,而是每种产品的单价 × 销量加总后,再除以总销量。直接用AVG(price)会忽略销量权重,结果完全失真。
核心公式是:SUM(price * quantity) / SUM(quantity) —— 这才是数学定义下的加权平均。
- 必须用
SUM()做分子和分母,不能拆成AVG()或MEAN() - 如果某产品
quantity为NULL,整行会被SUM()跳过(符合预期);但若price为NULL,price * quantity结果也是NULL,该行不参与计算——这点常被忽略 - MySQL、PostgreSQL、SQL Server、Oracle 都支持该写法,语法一致,无兼容性问题
GROUP BY时漏掉非聚合字段导致报错
想按产品类别算加权均价,却写了SELECT category, AVG(price)——这在严格模式下直接报错:ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause。
正确做法是把所有非聚合字段都放进GROUP BY,且聚合部分仍用SUM(price * quantity) / SUM(quantity):
SELECT category, SUM(price * quantity) / SUM(quantity) AS weighted_avg_price FROM sales GROUP BY category;
- 别试图用
ANY_VALUE()或关掉sql_mode来绕过——掩盖问题,不解决逻辑缺陷 - 如果还选了
product_name但没GROUP BY它,同样报错;哪怕你“知道它和category一一对应”,SQL引擎不认这个“知道” - PostgreSQL更严格:连
SELECT *在GROUP BY里都不允许,除非所有列都在GROUP BY中
quantity为0时除零风险与NULL处理
当某组数据的SUM(quantity)为0(比如测试数据全为0销量),SUM(price * quantity) / SUM(quantity)会触发除零错误,不同数据库表现不同:PostgreSQL报division by zero,MySQL默认返回NULL(取决于sql_mode)。
稳妥做法是显式拦截:
SELECT
category,
CASE
WHEN SUM(quantity) = 0 THEN NULL
ELSE SUM(price * quantity) / SUM(quantity)
END AS weighted_avg_price
FROM sales
GROUP BY category;
- 别依赖数据库默认行为——MySQL 8.0+在严格模式下也会报错
-
COALESCE(SUM(quantity), 0)没用:这里要判断的是求和结果是否为0,不是原始字段是否为NULL - 如果业务上quantity=0有意义(如“有报价但未成交”),需提前明确是否纳入计算;否则建议WHERE过滤掉
quantity
JOIN后重复计算引发的权重失真
常见错误:先JOIN orders ON product_id,再算加权平均,结果比实际高很多——因为一个产品在多笔订单里出现,quantity被重复累加了多次。
本质是维度错位:加权平均应在“产品×销量”原子粒度上计算,而不是“订单行×销量”。
- 确认
sales表本身是否已是聚合粒度(如每行代表一个产品在某时段的总销量);如果不是,先用子查询或CTE预聚合 - 如果必须从明细订单表出发,先
GROUP BY product_id汇总出SUM(quantity)和MAX(price)(或AVG(price),视业务而定),再在外层算加权平均 - 用
DISTINCT强行去重没用:SUM(DISTINCT price * quantity)语义错误,乘法不满足可去重性
加权平均看着简单,真正落地时最常栽在三处:误用AVG()代替SUM()/SUM()、GROUP BY字段遗漏、以及JOIN引入的隐式笛卡尔放大。只要盯住“权重必须是同一维度下的可加总量”,就不太会偏。










