空值不是一种东西
空值至少有三种语义:源端缺失导致未知、业务逻辑上不适用、以及度量本身为零。三者混用会让统计结果失真,也会让下游判断出错。规范做法是未知保留 null,不适用用明确的特殊成员或约定值,真正的零就存零,不要用 0、-1、1970-01-01 这些魔法值去兼任「空」。
基本信息
| 未知(源缺失) | 保留 null,不做默认值填充 |
|---|---|
| 不适用 | 用特殊成员或约定字符串(如 NONE) |
| 度量为零 | 存真实的 0,与缺失区分开 |
| 禁止 | 用 0 / -1 / 1970-01-01 混当空 |
| 统计口径 | count 与 sum 对 null 的处理差异要写明 |
典型使用场景
- 优惠券字段:未使用券是「不适用」,用 NONE 明确表达,而不是 null
- 发货时间:未发货属于「未发生」,保留 null,等发货再回填
- 退款金额:没有退款就是真实的 0,不能写成 null 让平均值失真
- 用户年龄:源端没采集是「未知」,保留 null 并允许下游按需过滤
- 日均值计算:先明确分母是总记录数还是非空记录数,两种口径结果不同
空值处理示例
空值处理示例
-- 区分「不适用」与「未发生」
SELECT order_id,
COALESCE(coupon_id, 'NONE') AS coupon_id, -- 不适用:明确无券
deliver_time, -- 未发货:保留 null
sum(refund_amt) AS refund_amt -- 未退款:真实 0
FROM dwd_trade_order_di
WHERE dt = '2026-10-12'
GROUP BY order_id, coupon_id, deliver_time;空值语义对照
| 场景 | 推荐取值 | 说明 |
|---|---|---|
| 源端未采集 | null | 保留未知,禁止填 0 |
| 逻辑不存在(无券) | 特殊成员 NONE | 可参与聚合不产生歧义 |
| 度量无发生(退款) | 0 | 是真实值,参与求和不失真 |
| 时间未发生(发货) | null | 避免参与时长计算 |
| 字符串未填写 | null 或空串择一 | 全链路统一,不混用 |
count(1) 统计行数,count(col) 跳过该列为 null 的行;指标口径里写「客户数」时必须说明用的是哪一种,否则同一个数两个人都能算出不同结果。同理,sum 会忽略 null,若先用 0 填充再求和,平均值就会被拉低。
落地检查项
- 列出全部度量字段,逐个确认「缺失」与「为零」是否需要用不同取值表达
- 检查历史脚本里有没有 COALESCE(x, 0) 这类无条件填充,区分哪些是有意为之
- 约定特殊成员的取值范围(如 NONE、UNKNOWN),并保证不与管理口径冲突
- 在字段注释里写明空值语义,让下游不必靠猜
用 1970-01-01 表示未发生的时间,会让时长统计出现五十年量级的异常值;做环比、留存、时效分析时这类数据必须先行剔除。