LEFT JOIN + WHERE IS NULL 是查拉链表单向缺失的唯一可靠写法,需基于业务主键+有效时间范围联合匹配,避免笛卡尔积,并确保复合索引、字段对齐及NULL安全比对。

LEFT JOIN + WHERE IS NULL 是查拉链表单向缺失的唯一可靠写法
拉链表(Slowly Changing Dimension Type 2)里同一业务主键可能有多条记录,靠自然键(如 user_id)无法直接一对一匹配,必须用**业务主键 + 有效时间范围**联合判断。只写 ON a.user_id = b.user_id 会生成大量笛卡尔积,结果不可信。
正确做法是:先锁定“当前有效”版本(通常 end_date = '9999-12-31' 或 is_current = 1),再基于该子集做 LEFT JOIN:
- 确保两表都加了复合索引:
(user_id, is_current)或(user_id, end_date) -
WHERE条件必须落在 JOIN 后的目标表字段上,例如WHERE b.user_id IS NULL,不能写WHERE b.end_date IS NULL - 如果源表用
start_date/end_date,目标表用valid_from/valid_to,字段名不一致时必须显式CAST或别名对齐,否则隐式转换导致匹配失败
MySQL 用户绕开 FULL OUTER JOIN 的实操模板
拉链表比对天然需要双向差异(源有目标无、目标有源无、同主键但属性不同),但 MySQL 不支持 FULL OUTER JOIN,硬写直接报错 ERROR 1054: Unknown syntax。必须用 LEFT JOIN + RIGHT JOIN + UNION ALL 拼接,且每部分都要带上来源标记和空值占位。
示例(对比 dim_user_src 和 dim_user_dst,主键为 user_skey):
SELECT 'src_only' AS diff_type, s.* FROM dim_user_src s LEFT JOIN dim_user_dst d ON s.user_skey = d.user_skey AND s.is_current = 1 AND d.is_current = 1 WHERE d.user_skey IS NULL <p>UNION ALL</p><p>SELECT 'dst_only' AS diff_type, d.* FROM dim_user_src s RIGHT JOIN dim_user_dst d ON s.user_skey = d.user_skey AND s.is_current = 1 AND d.is_current = 1 WHERE s.user_skey IS NULL;
注意:UNION ALL 不能换成 UNION——拉链表里重复 user_skey 是严重问题,去重会掩盖数据异常。
比对字段值差异时,NULL 和时间精度必须显式处理
拉链表里常见字段如 email、city、updated_at 都可能为 NULL,直接用 a.email != b.email 会导致所有含 NULL 的行被跳过。MySQL 和 PostgreSQL 处理方式不同:
- MySQL:用
(a.email != b.email) OR (a.email IS NULL) != (b.email IS NULL) - PostgreSQL:用
a.email IS DISTINCT FROM b.email(自动兼容NULL) - 时间字段(如
updated_at)要注意精度:MySQL 默认秒级,PostgreSQL 可能带微秒,比对前统一DATE_FORMAT(a.updated_at, '%Y-%m-%d %H:%i:%s')或TO_CHAR(a.updated_at, 'YYYY-MM-DD HH24:MI:SS')
字符串字段还要防前后空格和大小写干扰:TRIM(UPPER(a.name)) != TRIM(UPPER(b.name)) 才算真正不一致。
大表拉链比对必须加分区或增量过滤
全量拉链表动辄千万级,不加约束的 JOIN 会触发磁盘临时表甚至 OOM。生产环境必须限制范围:
- 按业务日期分区:
WHERE s.start_date >= '2026-06-01' AND s.end_date - 按主键分段:用
WHERE s.user_skey BETWEEN 100000 AND 200000分批执行 - 避免在 JOIN 条件里写函数(如
DATE(s.start_date)),会失效索引;改用范围条件 - 如果两表物理位置不同(如跨库),优先导出到同库临时表再比对;联邦查询网络抖动会导致结果断层
最易被忽略的一点:拉链表比对不是一次性的。只要源端有新版本产生,就必须重新跑差分逻辑——没有“比对完成”这回事,只有“本次比对快照”。

















