如何用SQL计算分组数据非空比例以评估数据稀疏度?
- 内容介绍
- 文章标签
- 相关推荐
本文共计823个文字,预计阅读时间需要4分钟。
在SQL中,没有内置名为`DENSITY`或`SPARSITY`的函数。所谓的稀疏度通常不是指数学密度,而是指数据在各个分组中的非空值占比。在业务上,通常表示为:
关键点在于:必须按分组计算,且要区分 NULL 和空字符串——很多误用把 COUNT(col) 当成“非空计数”,却忘了 COUNT() 本身就会跳过 NULL,但不会跳过 '';而 COUNT(*) 统计所有行,不管字段是否为空。
用 COUNT + CASE 算每组非空比例(最通用写法)
核心逻辑是:分母用 COUNT(*)(该组总行数),分子用显式条件判断哪些算“有效值”。不能依赖 COUNT(col) 直接替代,尤其当列允许空字符串或零值时。
-
COUNT(*)是安全分母,不遗漏任何行 - 分子推荐写成
COUNT(CASE WHEN col IS NOT NULL AND TRIM(col) != '' THEN 1 END)(文本列) - 数值列若把
0视为无效,就得加AND col != 0,否则0会被计入“非空” - MySQL 8.0+ / PostgreSQL / SQL Server 都支持,无兼容性风险
示例:统计各 dept 下 email 字段的非空比例
SELECT dept, COUNT(*) AS total, COUNT(CASE WHEN email IS NOT NULL AND TRIM(email) != '' THEN 1 END) AS non_empty_cnt, ROUND(100.0 * COUNT(CASE WHEN email IS NOT NULL AND TRIM(email) != '' THEN 1 END) / COUNT(*), 2) AS non_empty_pct FROM employees GROUP BY dept;
为什么不能直接用 COUNT(email) / COUNT(*)
看起来简洁,但隐患明显:
-
COUNT(email)只跳过NULL,不处理''(空字符串)、' '(纯空格)、'null'(字符串字面量) - 如果业务定义“邮箱为空字符串即无效”,那
COUNT(email)就高估了真实可用率 - 某些 ETL 流程会把缺失值补成
'N/A'或'UNKNOWN',这时COUNT(email)完全失效 - 聚合后无法还原原始语义:你不知道是
NULL多,还是脏字符串多
稀疏度高时的后续动作建议
发现某组 non_empty_pct < 10%,别急着删数据。先确认:
- 该列是否本就属于可选字段(如“员工紧急联系人电话”)?业务上允许大量为空
- 空值集中在某个子类(如
dept = 'Intern'),可能反映录入规则差异,而非质量问题 - 是否因上游系统未同步导致批量
NULL?需查变更日志,而非仅看快照 - 若真要清洗,优先用
UPDATE ... SET col = NULL WHERE TRIM(col) IN ('', 'N/A', 'NULL')统一归零,再走标准空值处理流程
真正容易被忽略的是:稀疏度指标本身需要和业务上下文绑定。同一列在销售表里 95% 空是异常,在历史归档表里 95% 空可能是正常状态。
本文共计823个文字,预计阅读时间需要4分钟。
在SQL中,没有内置名为`DENSITY`或`SPARSITY`的函数。所谓的稀疏度通常不是指数学密度,而是指数据在各个分组中的非空值占比。在业务上,通常表示为:
关键点在于:必须按分组计算,且要区分 NULL 和空字符串——很多误用把 COUNT(col) 当成“非空计数”,却忘了 COUNT() 本身就会跳过 NULL,但不会跳过 '';而 COUNT(*) 统计所有行,不管字段是否为空。
用 COUNT + CASE 算每组非空比例(最通用写法)
核心逻辑是:分母用 COUNT(*)(该组总行数),分子用显式条件判断哪些算“有效值”。不能依赖 COUNT(col) 直接替代,尤其当列允许空字符串或零值时。
-
COUNT(*)是安全分母,不遗漏任何行 - 分子推荐写成
COUNT(CASE WHEN col IS NOT NULL AND TRIM(col) != '' THEN 1 END)(文本列) - 数值列若把
0视为无效,就得加AND col != 0,否则0会被计入“非空” - MySQL 8.0+ / PostgreSQL / SQL Server 都支持,无兼容性风险
示例:统计各 dept 下 email 字段的非空比例
SELECT dept, COUNT(*) AS total, COUNT(CASE WHEN email IS NOT NULL AND TRIM(email) != '' THEN 1 END) AS non_empty_cnt, ROUND(100.0 * COUNT(CASE WHEN email IS NOT NULL AND TRIM(email) != '' THEN 1 END) / COUNT(*), 2) AS non_empty_pct FROM employees GROUP BY dept;
为什么不能直接用 COUNT(email) / COUNT(*)
看起来简洁,但隐患明显:
-
COUNT(email)只跳过NULL,不处理''(空字符串)、' '(纯空格)、'null'(字符串字面量) - 如果业务定义“邮箱为空字符串即无效”,那
COUNT(email)就高估了真实可用率 - 某些 ETL 流程会把缺失值补成
'N/A'或'UNKNOWN',这时COUNT(email)完全失效 - 聚合后无法还原原始语义:你不知道是
NULL多,还是脏字符串多
稀疏度高时的后续动作建议
发现某组 non_empty_pct < 10%,别急着删数据。先确认:
- 该列是否本就属于可选字段(如“员工紧急联系人电话”)?业务上允许大量为空
- 空值集中在某个子类(如
dept = 'Intern'),可能反映录入规则差异,而非质量问题 - 是否因上游系统未同步导致批量
NULL?需查变更日志,而非仅看快照 - 若真要清洗,优先用
UPDATE ... SET col = NULL WHERE TRIM(col) IN ('', 'N/A', 'NULL')统一归零,再走标准空值处理流程
真正容易被忽略的是:稀疏度指标本身需要和业务上下文绑定。同一列在销售表里 95% 空是异常,在历史归档表里 95% 空可能是正常状态。

