full join本身不直接标出差异,必须配合where筛选null字段:b.id is null标“仅左有”,a.id is null标“仅右有”,a.id = b.id且字段值不同标“同主键但内容异”。

FULL JOIN 本身不直接标出“差异”,得靠 WHERE 过滤
SQL 的 FULL JOIN 只是把左右表所有行都保留下来,空缺字段补 NULL;它不会自动告诉你哪几行是“只在左表”或“只在右表”。真正识别差异,必须配合 WHERE 子句判断 NULL 出现在哪一侧。
常见错误是写完 FULL JOIN 就停了,结果返回一堆含 NULL 的混合行,根本分不清哪些是新增、哪些是删除、哪些是更新。
- 只在左表(即“报表A有、B没有”)→
WHERE b.id IS NULL - 只在右表(即“报表B有、A没有”)→
WHERE a.id IS NULL - 两边都有但字段值不同(疑似更新)→
WHERE a.id = b.id AND (a.amount != b.amount OR a.name != b.name)(注意NULL比较需用IS DISTINCT FROM或显式处理)
ON 条件必须用稳定唯一键,别用模糊字段
差异比对的前提是能准确配对记录。如果 ON 用的是 name 或 email 这类可能重复或变更的字段,一次 FULL JOIN 就会错配甚至多对多爆炸,导致差异结果完全不可信。
理想情况是两张报表都有业务主键(如订单号 order_id、客户编码 cust_code),且该字段在两个来源中含义一致、格式一致、无空值。
- 若只有自然键(如姓名+手机号组合),先用
COALESCE或字符串拼接生成临时键,但要确认组合后唯一 - 若字段大小写/空格不一致(如报表A存
"John ",报表B存"john"),ON UPPER(TRIM(a.name)) = UPPER(TRIM(b.name))可能更安全 - 避免
ON a.timestamp::date = b.date这类跨类型隐式转换,易因时区或精度丢失配对
NULL 值比较必须显式处理,否则逻辑失效
当某字段在左表为 NULL、右表也为 NULL,用 a.field != b.field 判断会得到 UNKNOWN,被 WHERE 当作 false 过滤掉——这会导致“两边都是 NULL 却被误判为不同”或“本应算作相同却被跳过”。
PostgreSQL 支持 IS NOT DISTINCT FROM,MySQL 和 SQL Server 需手动展开:
WHERE (a.value IS NULL AND b.value IS NOT NULL) OR (a.value IS NOT NULL AND b.value IS NULL) OR (a.value != b.value)
- 简化写法(兼容多数数据库):
COALESCE(a.value, '') != COALESCE(b.value, ''),但要注意空字符串和NULL语义是否等价 - 数值型字段慎用
!=直接比,浮点误差可能导致误报,可改用ABS(a.val - b.val) > 0.001
性能差?加索引和限制范围再执行
FULL JOIN 是最重的连接类型,尤其两张大表(各百万行以上)不做约束时,可能跑十几分钟甚至 OOM。实际查差异,往往只关心“变化部分”,没必要全量扫。
- 先用
WHERE限定时间范围:比如只比对最近 30 天的订单,加AND a.created_at >= '2024-05-01' - 确保
ON字段(如id)在两张表上都有索引,特别是右表——很多引擎对FULL JOIN右侧索引利用率低 - 若只是找“新增/缺失”,用
LEFT JOIN + WHERE b.id IS NULL或NOT EXISTS往往更快,FULL JOIN真正必要场景其实是“要一次性看到三类差异”
最常被忽略的一点:两张报表的数据快照时间是否对齐?如果 A 表导出截止到 2024-05-31 23:59,B 表只到 2024-05-30,那查出来的“差异”可能只是同步延迟,不是真实数据问题。











