用窗口函数计算分组占比需用count() over()或count() over(partition by col)获取分母,配合显式类型转换和nullif防错,确保where条件在分子分母中一致。

用 COUNT() 和窗口函数算分组占比最直接
直接除法在 GROUP BY 里行不通,因为分母(总行数或某类总数)不在同一层级。必须用窗口函数把全局或分区级的聚合值“拉下来”参与计算。COUNT(*) OVER() 拿的是全表总行数,COUNT(*) OVER(PARTITION BY category) 拿的是各 category 内部行数——选哪个取决于你要算“该组占全表比例”还是“该组占其父类比例”。
常见错误是写成 COUNT(*) / COUNT(*),结果全是 1;或者漏写 OVER(),导致语法报错 ERROR: column "xxx" must appear in the GROUP BY clause。
- 要算“每个部门人数占全公司比例”:分母用
COUNT(*) OVER() - 要算“每个部门中男女人数各自占该部门比例”:分母用
COUNT(*) OVER(PARTITION BY dept) - 记得给分子加
::DECIMAL或乘以100.0,否则整数除整数结果为 0(PostgreSQL/SQL Server)或被截断(MySQL 默认)
MySQL 8.0+ 和 PostgreSQL 支持标准写法,旧版 MySQL 得绕路
MySQL 5.7 及更早不支持窗口函数,强行用子查询容易出错:嵌套里再查 COUNT(*) 会变成笛卡尔积,性能差还可能不准。推荐先算出总数存临时变量,或用 JOIN 关联汇总表。
PostgreSQL 和 MySQL 8.0+ 可直接写:
SELECT dept, COUNT(*) AS cnt, ROUND(COUNT(*) * 100.0 / COUNT(*) OVER(), 2) AS pct_of_total FROM employees GROUP BY dept;
注意 ROUND(..., 2) 控制小数位,不加可能输出一长串浮点误差数字(如 33.3333333333)。
SUM() 替代 COUNT() 算加权占比时别漏条件
如果要算“销售额占比”而非“订单数占比”,就不能只用 COUNT(*),得用 SUM(amount) 做分子,再配合 SUM(amount) OVER() 做分母。但要注意 WHERE 条件是否同步作用于两者——比如你加了 WHERE status = 'paid',那窗口函数里的 SUM() OVER() 也必须在同一过滤条件下执行,否则分母含未付款订单,占比就失真。
- 错误写法:
SUM(amount) / SUM(amount) OVER()且外部有 WHERE —— 分母没受 WHERE 影响 - 正确写法:确保整个查询的 WHERE 一致,或显式在窗口函数里用
FILTER (WHERE status = 'paid')(PostgreSQL)或CASE WHEN(通用) - 示例(PostgreSQL):
SUM(amount) FILTER (WHERE status = 'paid') / SUM(amount) FILTER (WHERE status = 'paid') OVER()
NULL 值和空分组会让百分比变诡异
COUNT(*) 不统计 NULL 行,但 COUNT(col) 也不统计 col 为 NULL 的行——如果你按 category 分组,而某些记录 category IS NULL,它们会被归到一个单独分组,但人容易忽略这个“空组”的存在,导致合计不到 100%。
更隐蔽的问题是:当某组完全没数据(比如 LEFT JOIN 后右表无匹配),COUNT(*) 返回 0,除以总和时变成 0%,但实际你可能希望它显示为 NULL 或跳过。这时候加 NULLIF(denominator, 0) 防止除零错误很关键:
ROUND(COUNT(*) * 100.0 / NULLIF(COUNT(*) OVER(), 0), 2)
另外,如果原始数据里有大量 NULL,又想按非空值归类,得先用 WHERE col IS NOT NULL 过滤,而不是依赖聚合函数自动忽略——逻辑意图必须明确。











