COUNT() OVER() 得全表总行数作分母,COUNT() OVER(PARTITION BY category) 得分组行数作分子,相除并乘100.0可算占比;需注意类型转换与数据库版本兼容性。

用 COUNT(*) OVER() 算总行数再除,是最直接的写法
窗口函数的核心优势是不改变原始行数,所以先算总数、再算分组数,最后相除就能得占比。关键不是“怎么写百分比”,而是“分母该用哪个窗口”——必须是全表总行数,不能是当前分组的行数。
常见错误是误用 COUNT(*) OVER(PARTITION BY category) 当分母,结果全是 100%;或者漏写 OVER(),导致语法报错 ERROR: window function requires an OVER clause。
- 分母:用
COUNT(*) OVER()(无PARTITION BY),得到全表总记录数 - 分子:用
COUNT(*) OVER(PARTITION BY category),得到每类记录数 - 注意类型转换:PostgreSQL/MySQL 8.0+ 默认整数除整数得整数,需显式转
DECIMAL或乘 100.0
MySQL 8.0+ 和 PostgreSQL 写法几乎一致,但 SQLite 不支持
SQLite 直到 3.25.0 才支持窗口函数,且部分聚合函数(如 COUNT() OVER())在旧版本中行为不稳定。如果你执行时报错 no such function: COUNT 或 near "OVER": syntax error,大概率是 SQLite 版本太低或未启用窗口函数编译选项。
PostgreSQL 和 MySQL 8.0+ 对该场景兼容性很好,可放心用。示例:
SELECT category, COUNT(*) AS cnt, ROUND(COUNT(*) * 100.0 / COUNT(*) OVER(), 2) AS pct FROM products GROUP BY category;
注意这里先 GROUP BY 再用窗口函数——因为 COUNT(*) OVER() 在聚合后仍能访问全表总数,这是窗口函数与 GROUP BY 共存的关键特性。
想保留小数但避免浮点误差?用 ROUND(..., 2) 或 CAST(... AS DECIMAL(5,2))
直接写 COUNT(*) * 100 / COUNT(*) OVER() 在某些数据库里会截断小数,比如结果是 33 而非 33.33。这不是计算错,是整数除法截断。
- 安全做法:统一乘
100.0(带小数点,触发浮点运算) - 更可控:用
ROUND(expr, 2)显式保留两位 - 严格场景:PostgreSQL 可用
CAST(COUNT(*) AS DECIMAL) * 100 / COUNT(*) OVER()
别依赖数据库默认精度——不同引擎对 / 运算符的隐式类型提升规则不一致,显式声明更可靠。
如果要按「某字段非空值」统计占比,WHERE 和窗口函数顺序很重要
比如统计每个分类下 price 非空的占比,不能把 WHERE price IS NOT NULL 放在最外层再算分母,否则分母变成过滤后的总数,就不是“占全部分类的百分比”了。
正确做法是:分母仍用全量 COUNT(*) OVER(),分子用条件聚合 COUNT(price) OVER(PARTITION BY category)(COUNT(列) 自动忽略 NULL)。
或者更清晰地写成:
SELECT category, COUNT(price) * 100.0 / COUNT(*) OVER() AS non_null_pct FROM products GROUP BY category;
这里 COUNT(price) 是分组内非空数量,COUNT(*) OVER() 是全表总行数——两者维度对齐,才是你要的真实占比。
真正容易被忽略的是:窗口函数里的 COUNT(*) OVER() 始终反映原始数据集规模,哪怕外面套了 GROUP BY 或 HAVING,它也不会跟着聚合收缩。这点和子查询完全不同。

















