分组内占比核心是“当前行值 ÷ 当前分组总和”,需用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 和 SQL Server 需处理类型。
SELECT category, product, amount, ROUND(amount * 100.0 / SUM(amount) OVER (PARTITION BY category), 2) AS pctFROM sales;
* 100.0 改用 * 100.00 控制精度amount 是 INTEGER,不乘 100.0 会导致结果全为 0(整除截断)INT 截断NULL 值时占比计算异常怎么办只要 amount 有 NULL,SUM() 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。