2. 空字符串与NULL混淆:清洗后统计口径不一致的排查
问题定位:同一列缺失值被写成两种表示
某订单明细表 orders 在清洗后需要统计“有效下单用户数”和“缺失手机号订单数”。清洗脚本对缺失手机号做了两件事:一部分记录被写成空字符串 '',另一部分保持数据库原生 NULL。随后用 COUNT(phone) 统计非缺失数、用 phone = '' 统计空串数,结果与原始表行数对不上。
关键差异在于 SQL 的三值逻辑:NULL 参与任何比较都返回 UNKNOWN,phone = '' 不会命中 NULL 行;而 COUNT(phone) 会跳过 NULL,却会把 '' 当作有效值计入。因此“缺失”被拆成两类后,任何单一条件都无法覆盖全部缺失行。
排查步骤:先量化两类缺失,再统一表示
- 分别统计两种缺失表示的行数,确认偏差来源与量级。
- 用“缺失总数 = 空串数 + NULL 数”与原始表缺失行数比对,验证是否还有第三种表示(如
'NULL'字符串、' '空格)。 - 在清洗阶段统一为
NULL,并同步修正所有下游统计条件。 - 重跑聚合,验证“有效数 + 缺失数 = 总行数”恒等式成立。
-- 1. 量化两类缺失(示例数据:总行 1000,缺失 120)
SELECT
COUNT(*) AS total_rows,
COUNT(phone) AS non_null_rows,
SUM(CASE WHEN phone = '' THEN 1 ELSE 0 END) AS empty_str_rows,
SUM(CASE WHEN phone IS NULL THEN 1 ELSE 0 END) AS null_rows,
SUM(CASE WHEN phone = 'NULL' OR TRIM(phone) = '' THEN 1 ELSE 0 END) AS other_bad_rows
FROM orders;
-- 2. 统一缺失表示:空串、纯空格、字面量 'NULL' 全部归一为 NULL
UPDATE orders
SET phone = NULL
WHERE phone = '' OR TRIM(phone) = '' OR UPPER(phone) = 'NULL';
-- 3. 统一后的统计口径
SELECT
COUNT(*) AS total_rows,
COUNT(phone) AS valid_phone_rows,
SUM(CASE WHEN phone IS NULL THEN 1 ELSE 0 END) AS missing_phone_rows
FROM orders;
统一表示后,
COUNT(phone) 与 phone IS NULL 天然互补,不再需要 OR phone = '' 这类易漏条件;这也是把“缺失”收敛到单一表示的核心收益。验证一致性:用恒等式与分组交叉核对
仅看总数一致还不够,需按业务维度分组核对,防止两类缺失在不同分组中分布不均导致局部口径仍错。下面用“有效数 + 缺失数 = 分组总数”逐组校验,任何一组不成立即说明该组仍存在未归一的缺失表示。
-- 分组恒等式校验:返回结果应为空集
SELECT region,
COUNT(*) AS grp_total,
COUNT(phone) AS grp_valid,
SUM(CASE WHEN phone IS NULL THEN 1 ELSE 0 END) AS grp_missing
FROM orders
GROUP BY region
HAVING COUNT(*) <> COUNT(phone) + SUM(CASE WHEN phone IS NULL THEN 1 ELSE 0 END);
-- 清洗前后缺失数对比(示例预期:清洗前 120,清洗后仍为 120,仅表示统一)
SELECT 'before' AS stage, COUNT(*) AS missing_cnt
FROM orders_bak WHERE phone = '' OR phone IS NULL
UNION ALL
SELECT 'after', COUNT(*) FROM orders WHERE phone IS NULL;
| 检查项 | 清洗前(示例) | 清洗后(示例) | 判定 |
|---|---|---|---|
| 空字符串行数 | 70 | 0 | 已归一 |
| NULL 行数 | 50 | 120 | 已合并 |
| 缺失总数 | 120 | 120 | 一致,未误删 |
| 有效数 + 缺失数 | 880 + 120 = 1000 | 880 + 120 = 1000 | 恒等式成立 |
若清洗后缺失总数小于清洗前,说明归一过程中误删了行(例如把空串行整行删除),需回滚并从备份表重新执行 UPDATE,而不是 INSERT 覆盖。
可复用检查清单
- 清洗规则中所有“缺失替换”是否统一指向
NULL,而非混用''。 - 下游统计是否仍残留
= ''条件,需改为IS NULL。 - 是否检查了
'NULL'字面量、纯空格等隐性缺失表示。 - 是否用分组恒等式校验,而非只看全局总数。
- 是否保留清洗前备份表,便于缺失数前后对比。