累积频率分布是各组占比按序累加的结果,需先计算每组占比(count()1.0/sum(count(*)) over()),再用sum() over(order by ...)累加;不可直接累加频数或嵌套窗口函数。

累积频率分布的本质是“当前行占比 + 前面所有行占比之和”
累积频率分布(Cumulative Frequency Distribution)不是简单地对频数求和,而是对**归一化后的比例**做窗口累加。很多人误用 SUM(count) 再除以总和,结果在分组或排序不稳时出错。关键在于:先算每组的占比(COUNT(*) * 1.0 / SUM(COUNT(*)) OVER()),再用 SUM() OVER(ORDER BY ...) 累加这个比例。
常见错误现象:SUM(COUNT(*)) OVER(ORDER BY x) 直接套用——这算的是累积频数,不是累积频率;若没加 1.0 强制浮点,整数除法会截断为 0。
- 必须确保
ORDER BY子句明确且与业务排序逻辑一致(如按销售额升序计算“低于该值的客户占比”) - 若数据含
NULL,ORDER BY默认把NULL排最前,可能扭曲分布起点,建议显式写ORDER BY x ASC NULLS LAST(PostgreSQL)或用COALESCE(x, -1e9)(MySQL/SQL Server) - 窗口帧默认是
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,无需显式声明,但别误加ROWS UNBOUNDED PRECEDING(语法错误)
PostgreSQL / MySQL 8.0+ / SQL Server 中的标准写法
三者语法高度一致,核心就是两层窗口:外层算总样本数,内层按序累加占比。以用户年龄分布为例:
SELECT age, COUNT(*) AS freq, COUNT(*) * 1.0 / SUM(COUNT(*)) OVER() AS freq_ratio, SUM(COUNT(*) * 1.0 / SUM(COUNT(*)) OVER()) OVER(ORDER BY age) AS cum_freq_dist FROM users GROUP BY age ORDER BY age;
注意:SUM(COUNT(*)) OVER() 是合法的——聚合函数嵌套窗口函数,只要外层是窗口、内层是聚合且有 GROUP BY 即可。MySQL 8.0+ 和 PostgreSQL 支持,但 SQLite 不支持(需子查询模拟)。
- 如果想按区间分组(如 0-10, 11-20),先用
CASE WHEN或FLOOR(age/10)生成分组键,再GROUP BY - SQL Server 中若用
DECIMAL类型,记得把1.0换成1.000避免精度丢失 - PostgreSQL 的
PERCENT_RANK()和CUME_DIST()是近似替代,但它们基于排名而非实际频数,当存在重复值时结果不同
遇到“窗口函数不能嵌套”报错怎么办
错误信息通常是 Window function cannot be used in window function(如旧版 MariaDB 或某些 Hive 版本)。这不是语法问题,而是引擎限制——它不允许在 OVER() 内部再出现窗口函数。此时必须拆成两层查询:
WITH base AS ( SELECT age, COUNT(*) AS freq FROM users GROUP BY age ), totals AS ( SELECT *, SUM(freq) OVER() AS total_cnt FROM base ) SELECT age, freq, freq * 1.0 / total_cnt AS freq_ratio, SUM(freq * 1.0 / total_cnt) OVER(ORDER BY age) AS cum_freq_dist FROM totals;
这种写法兼容性更好,也更易调试。重点在于:第一层只做聚合,第二层才引入窗口计算;total_cnt 必须在 totals CTE 中算出,不能在最终 SELECT 里重复写 SUM(freq) OVER(),否则又触发嵌套限制。
- 某些旧版 Spark SQL 要求用
SELECT ... FROM (SELECT ...) t替代 CTE - 如果表极大,CTE 可能被物化两次,可改用变量(MySQL)或临时表(SQL Server)缓存
total_cnt
用 CUME_DIST() 真的更简单吗
CUME_DIST() 返回“小于等于当前值的行数占比”,看起来像累积频率,但它统计的是**原始行数**,不是分组后的频数。比如 100 个用户中 5 人年龄=25,CUME_DIST() 对这 5 行都返回同一个值(即 ≤25 的用户数 / 总用户数),而你通常想要的是“年龄 ≤25 的人群占总人群的比例”——这二者仅在无重复值时相等。
所以:CUME_DIST() 适合未分组的连续值分析(如订单金额排序),不适合分组频数统计场景。真正需要累积频率分布时,老老实实走 COUNT + SUM() OVER() 路线更可控。
- 用
CUME_DIST()前先确认:你的 x 轴是原始字段值(如每个用户的 age),还是分组标签(如年龄段)?后者必须手动聚合 -
PERCENT_RANK()的分母是 n−1,首尾值固定为 0 和 1,和累积频率定义不符,别混用 - 有些 BI 工具(如 Metabase)自动生成的 “cumulative %” 图表底层就用了
CUME_DIST(),导出 SQL 时要注意是否符合业务口径
实际写的时候,最容易被忽略的是分组与排序的粒度是否匹配——比如按日期分组却按时间戳排序,或者漏了 GROUP BY 导致窗口在未聚合数据上计算。一旦发现累积和超过 1.0 或出现突变,优先检查这两点。










