如何用SQL开窗函数计算分组内的占比情况?

作者:袖梨 2026-07-10
分组内占比核心是“当前行值 ÷ 当前分组总和”,需用SUM() OVER(PARTITION BY ...)广播分组和作分母,分子为原始字段,须处理类型转换与NULL以避免截断或除零错误。

SUM() OVER() 计算分组内占比的核心逻辑

分组内占比本质是「当前行值 ÷ 当前分组总和」,必须用开窗函数把分组总和“广播”到每一行。不能用 GROUP BY 配合聚合后 JOIN,那样会丢失明细行;也不能用子查询,性能差且难维护。

关键点在于:分母必须是带 PARTITION BY 的窗口聚合,分子是当前行原始字段值。

  • SUM(amount) OVER (PARTITION BY category) 给出每个 category 内的总金额
  • 分子直接写 amount,不要加任何聚合或窗口
  • 结果需显式转为 DECIMAL 或乘以 1.0 避免整数截断(尤其在 PostgreSQL / SQL Server 中)

MySQL 8.0+ 和 PostgreSQL 中的写法差异

主流新版数据库语法一致,但默认除法行为不同:MySQL 8.0+ 默认返回小数,PostgreSQL 和 SQL Server 需处理类型。

SELECT   category,   product,   amount,  ROUND(amount * 100.0 / SUM(amount) OVER (PARTITION BY category), 2) AS pctFROM sales;
  • MySQL 可省略 * 100.0 改用 * 100.00 控制精度
  • PostgreSQL 若 amountINTEGER,不乘 100.0 会导致结果全为 0(整除截断)
  • SQL Server 同样需至少一个操作数为浮点类型,否则返回 INT 截断

遇到 NULL 值时占比计算异常怎么办

只要 amountNULLSUM() OVER() 会自动忽略它——这通常符合预期;但若整组都是 NULL,分母为 NULL,导致整个占比为 NULL

  • COALESCE(SUM(amount) OVER (...), 0) 把分母补 0,但需配合 CASE 避免除零错误
  • 更稳妥写法:CASE WHEN SUM(amount) OVER (PARTITION BY category) = 0 THEN 0 ELSE ... END
  • 如果业务要求把 NULL 视为 0 参与统计,先用 COALESCE(amount, 0) 替换分子分母中的原始字段

想按多个维度分组(比如城市 + 年份)怎么写

PARTITION BY 支持多列,顺序无关,但必须和业务分组逻辑完全一致。漏掉一列就会跨组计算,结果完全错误。

  • 正确:PARTITION BY city, YEAR(order_date)
  • 错误:PARTITION BY city(漏了年份,导致跨年累加)
  • 注意:窗口函数中不能用列别名,YEAR(order_date) 必须写原表达式,不能写成 yr
  • 若需排序后取累计占比(如帕累托分析),在 OVER() 里加 ORDER BY,但此时分母仍是全组和,不是前缀和

实际写的时候最容易卡在分母类型和 NULL 处理上,尤其是从旧版 MySQL 迁移或对接不同数据库时,看似一样的 SQL,跑出来全是 0 或全是 NULL。

相关文章

精彩推荐