直接用sum(col) over(partition by group_col)仅得组内总和,占比需当前行值除以该总和;常见错误包括整数除法截断、null导致结果为0或null,须用100.0*coalesce(col,0)/nullif(sum(...),0)并round控制精度。

为什么 SUM() OVER(PARTITION BY ...) 算不出正确占比?
直接用 SUM(col) OVER(PARTITION BY group_col) 只能得到分区总和,但占比需要「当前行值 / 分区总和」——如果在同一个 SELECT 中混用未聚合列和窗口函数,又没处理好数据类型或空值,结果常是 0 或 NULL。常见错误是写成 col / SUM(col) OVER(...) 却忘了 SQL 除法默认截断整数,或者 col 为 NULL 导致整行占比变 NULL。
- 确保分子分母类型一致:至少一方转为
FLOAT、DECIMAL或NUMERIC - 用
COALESCE(col, 0)防止分子为空;用NULLIF(SUM(...) OVER(...), 0)防止除零 - 别在
WHERE中过滤掉会影响分区总和的行——窗口函数在WHERE之后执行,但分区总和只基于最终结果集
怎么写一个带四舍五入的分区占比(如销售占比)?
典型场景:按 region 分区,算每个 product 的销售额占该地区总额的百分比,并保留两位小数。关键不是套函数,而是控制计算顺序和精度。
SELECT
region,
product,
sales,
ROUND(
100.0 * COALESCE(sales, 0) / NULLIF(SUM(COALESCE(sales, 0)) OVER(PARTITION BY region), 0),
2
) AS sales_pct
FROM sales_table;
-
100.0 *强制提升为浮点运算,避免整数除法归零 -
COALESCE(sales, 0)把空销售额当 0 算,不污染占比分母 -
NULLIF(..., 0)让分母为 0 时返回NULL,而非报错 -
ROUND(..., 2)在最后一步四舍五入,别在中间 ROUND 分子或分母
SUM() OVER() 和 GROUP BY 混用会怎样?
窗口函数和分组聚合可以共存,但逻辑层级不同:GROUP BY 先压缩行,SUM() OVER() 再基于压缩后的结果做分区计算——这通常不是你想要的。比如对每个 region 先 GROUP BY region, product 求总销量,再想算各产品占本区域比例,此时必须确保 SUM() OVER(PARTITION BY region) 的粒度与外层 GROUP BY 对齐,否则分区总和会重复或漏算。
- 若已用
GROUP BY region, product,则SUM() OVER(PARTITION BY region)是安全的,因为每组对应一个 product,分区仍按 region 划分 - 但若
GROUP BY region后还想显示原始明细行,就不能靠GROUP BY,得全用窗口函数+去重逻辑 - MySQL 8.0+、PostgreSQL、SQL Server 支持,但 SQLite 不支持窗口函数(除非 3.25+ 且编译时启用)
分区占比在报表中突然跳变,可能是哪些隐性问题?
比如某天报表里某个 region 的占比总和不是 100%,差 0.01% —— 这往往不是计算错,而是浮点精度叠加或 ROUND 截断导致的显示误差。真实值加总仍是 100%,但四舍五入后各行相加可能为 99.99 或 100.01。
- 不要对占比列再求和;要验证,应还原为原始值:用
SUM(sales) / SUM(SUM(sales) OVER(PARTITION BY region))校验 - 前端展示时,可对最后一行占比做“补足”处理(如把剩余差额加给最大项),但数据库层保持原始计算
- 注意时区和数据延迟:如果分区依据字段(如
order_date)含时间部分,而业务按天统计,记得用CAST(order_date AS DATE)或DATE(order_date)统一粒度
实际写的时候,最易被忽略的是分母是否真的覆盖了你要比的全部范围——比如按月份分区,但数据里混入了未来日期或 NULL 日期,那这部分行会被踢出所有分区,导致分母偏小、占比虚高。










