组内占比 = 当前行值 ÷ 该组所有行的聚合和,需用SUM() OVER(PARTITION BY ...)将分组和广播至每行再除;须用COALESCE处理NULL、NULLIF防除零、乘1.0避免整数截断。

用 SUM() OVER(PARTITION BY ...) 算组内占比的核心逻辑
组内占比 = 当前行值 ÷ 该组所有行的聚合和。关键不是“用什么函数”,而是得先用 SUM() OVER(PARTITION BY ...) 把每组的总和“广播”到组内每一行,再做除法。直接写 SUM(col) / SUM(col) OVER(...) 就行,不用子查询或JOIN。
常见错误:忘记处理 NULL 和除零
如果分母(组内和)为 0,结果会是 NULL 或报错(取决于数据库),而分子为 NULL 时整个占比也是 NULL。必须显式兜底:
- 用
COALESCE(col, 0)把分子的空值转成 0(按业务需求决定是否合理) - 分母用
NULLIF(SUM(...) OVER(...), 0)避免除零,再套一层COALESCE(..., 0)统一返回 0 - PostgreSQL/SQL Server 支持
ROUND(..., 4)控制小数位;MySQL 8.0+ 同样适用,但旧版需用FORMAT()(注意它返回字符串)
不同数据库对 PARTITION BY 的兼容性细节
PARTITION BY 本身是 SQL:2003 标准,主流数据库都支持,但边界行为有差异:
- SQLite 直到 3.25.0 才支持窗口函数,且不支持
ORDER BY在OVER中与聚合函数混用(比如SUM() OVER(PARTITION BY x ORDER BY y)会报错) - MySQL 8.0+ 完全支持,但
PARTITION BY表达式不能是别名(必须写原始列名或表达式) - Oracle 允许在
PARTITION BY中用函数,如PARTITION BY TRUNC(order_date, 'MM'),但某些旧版本对嵌套函数有限制
实际例子:按部门算每人销售额占部门总额比
假设表 sales 有 dept、emp_id、amount 三列:
SELECT
dept,
emp_id,
amount,
ROUND(
COALESCE(amount, 0) * 1.0 /
NULLIF(SUM(COALESCE(amount, 0)) OVER(PARTITION BY dept), 0),
4
) AS pct_in_dept
FROM sales;这里乘以 1.0 是为了触发浮点除法(尤其在 SQL Server 或旧版 MySQL 中避免整数截断);COALESCE 套两层是兼顾分子空值和分母为 0 的情况。真正容易被忽略的是:一旦 PARTITION BY 的字段本身含 NULL,这些 NULL 会被当作同一组——如果业务上“部门为空”不应参与占比计算,得提前过滤或用 CASE WHEN dept IS NOT NULL THEN dept END 作为分区键。

















