空值与默认值陷阱
空值与默认值看起来都是“没填”,含义却不同:NULL 表示未知,默认值(空串、0、1970 时间戳)表示填了个占位符。后者会把缺失伪装成有数据,让 not_null 一类的校验全部通过。校验要同时查 NULL 与可疑默认值,并把聚合时的 NULL 处理口径写进文档,否则跨团队对账时各算各的。
基本信息
| NULL 语义 | 未知值,参与 COUNT/AVG 时被忽略 |
|---|---|
| 常见默认值 | 空串、0、-1、1970-01-01 00:00:00 |
| 检查方式 | 分别统计 NULL 条数与默认值占比 |
| 聚合口径 | COUNT/AVG 忽略 NULL;SUM 返回 NULL 表示全为 NULL |
| 影响面 | 指标口径、对账结果、下游判空逻辑 |
典型使用场景
- 上游用空串代替 NULL 时,检查 not_null 规则是否被绕过
- 时间字段出现 1970 时间戳,判断是否为默认占位值
- 金额为 0 的记录激增时,区分真实零值与默认填充
- 跨团队对账前统一 NULL 口径,避免 AVG 结果对不上
- 迁移或同步后抽查默认值占比,验证类型映射是否正确
- 下游关联查询结果变少时,排查是否因 NULL 参与等值比较而不匹配
同时检查 NULL 与默认值
同时检查 NULL 与默认值
SELECT
SUM(CASE WHEN amount IS NULL THEN 1 ELSE 0 END) AS null_cnt, -- 真缺失
SUM(CASE WHEN amount = 0 THEN 1 ELSE 0 END) AS zero_cnt, -- 可疑默认值
SUM(CASE WHEN created_at = '1970-01-01 00:00:00' THEN 1 ELSE 0 END) AS epoch_cnt
FROM dwd_orders
WHERE dt = '2026-10-11';占比与口径差异
占比与口径差异
-- 占比:只看绝对条数会被大表体量稀释
SELECT
COUNT(*) AS total,
SUM(CASE WHEN amount IS NULL THEN 1 ELSE 0 END) / COUNT(*) AS null_ratio
FROM dwd_orders
WHERE dt = '2026-10-11';
-- 口径差异:AVG 忽略 NULL 与把 NULL 当 0 参与计算的结果并不相同
SELECT AVG(amount) AS avg_ignore_null,
SUM(amount) / COUNT(*) AS avg_null_as_zero
FROM dwd_orders
WHERE dt = '2026-10-11';NULL 与默认值对照
| 表现 | 真实含义 | 处置 |
|---|---|---|
| NULL | 未知或未采集 | 按口径决定是否参与聚合 |
| 空串 | 上游用占位符代替 NULL | 写规则识别,必要时归一为 NULL |
| 0 或 -1 | 默认填充或真实零值 | 结合业务分布判断,勿一律当缺失 |
| 1970-01-01 | 时间字段默认占位 | 回溯上游采集逻辑,标记为无效 |
指标的 NULL 口径要写进文档:AVG 忽略 NULL 与把 NULL 当 0 的结果差异很大,跨团队口径不同会导致对账争议。
不同引擎的默认行为不同:MySQL 在非严格模式下会把非法值静默转成默认值,仅告警不报错,建表与导入时要留意 sql_mode 设置。
判空写法要统一:NULL 只能用 IS NULL 判断,写成 = NULL 恒不成立;空串与 NULL 混用的字段,先归一再统计,否则同一份数据会得出两套缺失率。