首页/数据/数仓建模速查/SCD 属性比较的 NULL 安全写法
维度与事实设计149 条

SCD 属性比较的 NULL 安全写法

普通等值比较遇到 NULL 返回未知,属性含空值时变更检测会漏判;各引擎的 NULL 安全比较语法不一致,迁移时要复核。

数仓建模速查数据更新于 2026-10-13

NULL 比较为什么会漏判

SCD 的变更检测靠逐列比较属性值,而普通等值比较遇到 NULL 返回的是未知而不是真:当属性从空值变成有值,或者两行都为空时,普通比较无法判定,变更就被漏掉。各引擎的 NULL 安全比较语法不同,跨引擎迁移时必须逐条复核。

基本信息

MySQLa <=> b 做 NULL 安全等值比较
PostgreSQLa IS NOT DISTINCT FROM b
Spark SQL同时支持 <=> 与 IS NOT DISTINCT FROM
Hive旧版本无专用运算符,用 coalesce 兜底
替代做法入维前把空值规整为「未知」占位

典型使用场景

  • SCD2 变更检测:比较跟踪列判断是否需要产生新版本
  • 指纹计算:拼接前统一处理空值,避免指纹不稳定
  • 跨引擎迁移:从 Hive 迁到 Spark 或 Doris 时复核比较语义
  • 数据校验:比对两表差异时避免因空值被误判为不同
  • 增量同步:空值处理错误会导致每次都全量同步

NULL 安全比较

NULL 安全比较
-- 找出 name 或 phone 实际发生变化的当前行(MySQL 8 语法)
SELECT d.customer_id
FROM dim_customer d
JOIN stg_customer s ON d.customer_id = s.customer_id
WHERE d.is_current = 1
  AND NOT (d.name <=> s.name)
  AND NOT (d.phone <=> s.phone);

-- 通用写法:把空值规整为占位符后比较
-- WHERE coalesce(d.name, '#N/A') <> coalesce(s.name, '#N/A')

各引擎写法

引擎NULL 安全写法备注
MySQLa <=> bMySQL 8 常用写法
PostgreSQLIS NOT DISTINCT FROM标准语法,可读性好
Spark SQLa <=> b两种写法都支持
Hivecoalesce 兜底旧版本无专用运算符
把空值视为有效值时,先落默认值(如「未知」)再比较能简化逻辑,但占位符约定必须统一写进维度规范,否则各表占位不一致反而更难排查。
拼接指纹前先统一空值表示:空串与 NULL 的拼接结果不同,会让同一业务状态在不同批次算出不同指纹,产生大量伪变更。
入维前统一空值占位比事后逐列比较更省事:把空串、NULL 以及字符串形式的空值都规整成同一占位符,可以一次性消除大部分伪变更。

易错点

  • 直接用等号比较属性:含空值的行永远不判为变更,历史版本缺失
  • 忽略引擎差异:跨引擎迁移后结果漂移,同一批数据出现两个结论
  • 空串与 NULL 混用:指纹不稳定,每次加载都产生伪变更
  • 比较时未统一类型(数字与字符串):相同取值被判为不同
  • 只比较部分列:跟踪列清单不全,部分属性变更从历史记录中消失
  • 比较时忽略大小写与首尾空格:同一业务值因格式差异被判为变更,版本无谓增加

数据来源与参考

  • MySQL 8.0 参考手册:NULL 安全等值;Spark SQL 函数参考
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌