应使用COUNT(DISTINCT col)/COUNT(*)在GROUP BY下计算,分母必须是组内行数而非全表;需显式转浮点防整除,注意NULL被自动忽略而空字符串会计入。

SQL里怎么算每个分组的COUNT(DISTINCT)占总行数的比例
直接用 COUNT(DISTINCT col) 除以 COUNT(*) 就行,但必须在 GROUP BY 下做,否则分母是全表总数,不是组内总数。常见错误是漏写 GROUP BY 或把两个聚合写在不同层级导致语法报错。
典型场景:分析用户行为日志中,每个 user_id 的操作类型(action_type)有多“分散”——比如一个用户只点一次“购买”,比例是 1/1 = 1;另一个用户点了 5 次,但全是“浏览”,比例就是 1/5 = 0.2。
- 分母必须是组内行数:
COUNT(*),不能用子查询算全表再关联,否则性能差且逻辑错 - 分子用
COUNT(DISTINCT action_type),注意 NULL 值会被自动忽略(符合多数业务预期) - 除法结果默认是整数(尤其在 MySQL 5.7 或 SQLite 中),要显式转成浮点:
CAST(COUNT(DISTINCT action_type) AS REAL) / COUNT(*)或1.0 * COUNT(DISTINCT action_type) / COUNT(*)
SELECT user_id, CAST(COUNT(DISTINCT action_type) AS REAL) / COUNT(*) AS distinct_ratio FROM user_log GROUP BY user_id;
PostgreSQL/MySQL 8.0+/SQL Server 怎么避免重复扫描提升性能
如果表很大,又需要同时统计多个维度的去重比例(比如既要按 user_id 算,又要按 date 算),多次 GROUP BY 会反复扫描。这时候可以用窗口函数 + 子查询,只扫一遍。
核心思路:先用窗口函数算出每个分组的 COUNT(*) 和 COUNT(DISTINCT),再在外层做除法。但注意:窗口函数不支持 COUNT(DISTINCT) 直接开窗(PostgreSQL 除外),得用 DENSE_RANK() 或 ROW_NUMBER() 配合技巧绕过。
- PostgreSQL 可直接:
COUNT(DISTINCT action_type) OVER (PARTITION BY user_id) - MySQL 8.0+ 和 SQL Server 需改写:对
(user_id, action_type)去重后,再按user_id计数 - 更稳的通用写法是两层子查询:内层
SELECT DISTINCT user_id, action_type,外层GROUP BY user_id再关联原表计总数
遇到 ERROR: aggregate function calls cannot contain aggregate function calls 怎么办
这是 PostgreSQL 报的错,意思是试图在聚合函数里再套聚合函数,比如写成 AVG(COUNT(DISTINCT x))。但你要的只是单个分组内的比例,根本不需要嵌套聚合——这个错误通常是因为误把 GROUP BY 放到了子查询外,或用了错误的 CTE 结构。
- 检查是否在
SELECT里写了类似COUNT(DISTINCT COUNT(*))这种非法组合 - 确认所有聚合都落在同一级
GROUP BY下,不要试图“先分组再对分组结果聚合”除非真需要上卷(rollup) - 如果真要算所有分组的平均 distinct_ratio(即“平均唯一性”),得先存中间结果,再查一次:
SELECT AVG(distinct_ratio) FROM (SELECT ..., COUNT(DISTINCT x)/COUNT(*) AS distinct_ratio FROM t GROUP BY g) t2
NULL 值和空字符串对 COUNT(DISTINCT) 比例的影响
COUNT(DISTINCT col) 默认跳过 NULL,但不会跳过空字符串 ''——这两者在业务语义上可能都代表“未知”,但数据库认为它们不同。结果就是:含大量 NULL 的列,COUNT(DISTINCT) 偏小;含大量 '' 的列,COUNT(DISTINCT) 可能虚高。
- 若想把 NULL 当作一种有效取值参与去重,改用
COUNT(DISTINCT COALESCE(col, 'NULL')) - 若想统一清理空字符串和 NULL,用
COUNT(DISTINCT NULLIF(TRIM(col), '')),再配合COALESCE补默认值 - 比例结果接近 0 或 1 时,务必抽样检查原始数据分布,避免被脏数据误导判断
真正麻烦的不是公式怎么写,而是搞清业务里“唯一性”到底指什么:是字段原始取值的差异度?还是去除无效值后的实际行为多样性?后者往往需要先清洗,再计算。










