必须用COUNT(*) OVER (PARTITION BY ...)实现分组行数广播,不可仅用GROUP BY;算比例需避免整数除法截断,应转浮点;窗口函数与GROUP BY需配合CTE或子查询;注意NULL、零分母及多级分组边界。

用 COUNT(*) 窗口函数算分组内行数
直接在 GROUP BY 后用 COUNT(*) 只能得到每组总行数,没法让每行都带上这个总数。必须用窗口函数——COUNT(*) OVER (PARTITION BY ...) 才能把分组总数“广播”到该组每一行。
常见错误是写成 COUNT(*) OVER (PARTITION BY col) 却忘了加 ORDER BY 或 ROWS BETWEEN,其实这里不需要排序或帧定义,纯聚合型窗口函数默认就是整组范围,不加额外子句反而最安全。
- 如果分组字段是
category,就写COUNT(*) OVER (PARTITION BY category) - 别写成
COUNT(*) OVER (PARTITION BY category ORDER BY id),除非你真需要累计计数 - 注意:MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 3.25+ 支持,旧版不支持
用 CAST 或 1.0 * 避免整数除法截断
算比例时,count_this_row / count_total 如果两边都是整数,结果会是整数(比如 1/3 → 0),必须至少一边转为浮点。
不同数据库写法略有差异,但核心原则一致:打破整数除法规则。
- PostgreSQL / SQL Server:用
CAST(count_this_row AS DECIMAL(5,4)) / COUNT(*) OVER (...) - MySQL:更简单,直接
count_this_row * 1.0 / COUNT(*) OVER (...) - 避免只写
count_this_row / 1.0——这没用,分母还是整数窗口函数结果
结合 GROUP BY 和窗口函数的典型写法
不能在同一个查询层级里既 GROUP BY 又直接引用未聚合列 + 窗口函数,得要么用子查询,要么用 CTE。最稳妥的是 CTE 写法,逻辑清晰且兼容性好。
WITH grouped AS ( SELECT category, COUNT(*) AS cnt FROM orders GROUP BY category ) SELECT category, cnt, cnt * 1.0 / SUM(cnt) OVER() AS ratio FROM grouped;
这段代码先按组聚合出数量,再用窗口函数算全局占比。如果要算“每行在所属分组内的占比”,那原始表就得保留明细行,窗口函数放在主查询里:
SELECT
user_id,
category,
COUNT(*) OVER (PARTITION BY category, user_id) AS user_cnt_in_cat,
COUNT(*) OVER (PARTITION BY category) AS cat_total,
COUNT(*) OVER (PARTITION BY category, user_id) * 1.0
/ COUNT(*) OVER (PARTITION BY category) AS ratio
FROM orders;容易漏掉的边界情况
空值、零计数、多层嵌套分组都会让比例计算出意外结果。
-
PARTITION BY字段含NULL:大多数数据库把所有NULL归为同一组,但行为不统一,建议提前COALESCE(category, 'unknown') - 某组总行数为 0:实际不会发生(
COUNT(*)最小是 1),但如果用其他聚合如SUM(x)做分母,就得加NULLIF(..., 0) - 想按月+类别双重分组?写成
PARTITION BY YEAR(dt), MONTH(dt), category,别漏字段顺序或括号

















