聚合运算必须严格匹配分组层级,SUM(a)+SUM(b)与SUM(a+b)语义不同;除法需用NULLIF和COALESCE防错;WHERE不能用聚合,须改HAVING;跨分组运算应使用窗口函数、子查询或CTE。

可以直接在 SELECT 列表里对聚合函数结果做加减乘除,但必须确保运算发生在同一分组层级,且 NULL 处理得当——否则结果会意外为 NULL 或丢失整行。
聚合函数间直接四则运算的写法
常见错误是写成 SUM(a) + SUM(b) 和 SUM(a + b) 混用,二者语义不同:
-
SUM(a) + SUM(b):先分别对列a、b聚合求和,再相加。若某行a为NULL、b非空,该行仍参与SUM(b)计算 -
SUM(a + b):先逐行计算a + b,任一列为NULL时整行结果为NULL,该行被跳过(因NULL + 10 = NULL),最终SUM()忽略所有含NULL的行
例如想算“总销售额 - 总退款额”,应写 SUM(sales) - SUM(refund);若写 SUM(sales - refund),只要某条记录退款为 NULL,这笔销售就完全不计入结果。
涉及除法时必须处理除零和 NULL
像计算“客单价 = 总销售额 / 订单数”这种场景,AVG() 不能替代:AVG(amount) 是每单平均,而 SUM(amount)/COUNT(*) 才是严格意义上的客单价。但直接写会出问题:
- 当
COUNT(*) = 0(空分组)时,除法报错或返回NULL(取决于数据库) - 若
SUM(amount)或COUNT(*)本身为NULL(如全行为NULL),结果也为NULL
安全写法是:
COALESCE(SUM(amount), 0) * 1.0 / NULLIF(COUNT(*), 0)
其中 NULLIF(COUNT(*), 0) 在分母为 0 时返回 NULL,整个表达式结果为 NULL;乘 1.0 是为避免整数除法截断(尤其在 PostgreSQL/SQL Server 中)。
WHERE/HAVING 对聚合运算的影响边界
聚合运算只能放在 SELECT 或 HAVING 子句里,不能用于 WHERE ——这是初学者最常踩的坑。比如想筛选“毛利率 > 30%”的品类,不能写:
WHERE (SUM(revenue) - SUM(cost)) / SUM(revenue) > 0.3
必须改用 HAVING:
HAVING (SUM(revenue) - SUM(cost)) * 1.0 / NULLIF(SUM(revenue), 0) > 0.3
注意两点:
-
HAVING中出现的字段必须在SELECT列表中(或为分组字段),否则报错 - 所有参与运算的聚合函数必须与
GROUP BY维度一致,跨维度运算(如“本城市销量 / 全国销量”)需用窗口函数或子查询
需要跨分组做运算时别硬套 GROUP BY
比如要算“各城市销量占全省比例”,直接在 GROUP BY city 下无法拿到全省总量。这时有三个可靠路径:
- 用窗口函数:
SUM(sales) OVER() AS total_sales,再在SELECT里算SUM(sales) * 1.0 / SUM(sales) OVER() - 用子查询:把全省总量查出来作为常量,例如
(SELECT SUM(sales) FROM t) AS province_total - 用 CTE 预先算好各级汇总,再
JOIN关联(适合多级钻取,如省→市→区)
硬拼 UNION ALL 或多次 GROUP BY 再 JOIN 容易丢数据、难维护,且性能差——尤其是当分组键基数高时,笛卡尔积风险真实存在。
真正麻烦的不是写法本身,而是搞清“这个运算到底该在行级、组级还是全局级发生”。一旦层级错,结果可能偏差几倍,而 SQL 不报错,只默默给你一个看似合理实则错误的数字。

















