full outer join是找对账差异最直接方式,因其天然覆盖a有b无、b有a无、两边都有但值不同三类情况,结合case打标和coalesce处理空值,并用where过滤出问题行,可一次性精准定位差异。

为什么FULL OUTER JOIN是找对账差异的最直接方式
因为对账的核心诉求是“两边都得看到”:A表有但B表没有的、B表有但A表没有的、两边都有但字段值不同的记录,全都要暴露出来。FULL OUTER JOIN天然覆盖这三类情况,比写两个LEFT JOIN再UNION更简洁,也比用NOT EXISTS逐条排查更直观。
注意:MySQL不支持FULL OUTER JOIN,必须用LEFT JOIN + RIGHT JOIN + UNION ALL模拟;PostgreSQL、SQL Server、Oracle原生支持。
怎么写才能一次性标出三类差异
关键不是只连表,而是用CASE把差异类型打上标签,并用COALESCE统一空值便于比较。比如对账字段是order_id和status:
SELECT
COALESCE(a.order_id, b.order_id) AS order_id,
a.status AS status_a,
b.status AS status_b,
CASE
WHEN a.order_id IS NULL THEN '仅B表存在'
WHEN b.order_id IS NULL THEN '仅A表存在'
WHEN a.status != b.status THEN '状态不一致'
ELSE '一致'
END AS diff_type
FROM table_a a
FULL OUTER JOIN table_b b ON a.order_id = b.order_id
WHERE a.order_id IS NULL
OR b.order_id IS NULL
OR a.status != b.status;
这个WHERE条件过滤掉完全一致的记录,只留问题行——否则结果里90%是“一致”,反而干扰排查。
- 务必用
COALESCE(a.order_id, b.order_id),避免主键列显示NULL -
status字段如果有NULL,a.status != b.status会判为FALSE(NULL != NULL为UNKNOWN),得改成NOT (a.status b.status)(MySQL)或NOT (a.status = b.status AND (a.status IS NOT NULL OR b.status IS NOT NULL)) - 如果表很大,确保
order_id上有索引,否则JOIN会慢到超时
实际对账时最容易漏掉的坑
对账不是比单个字段,而是业务逻辑层面的“等价”。常见疏漏:
-
status值域不一致:A表用'paid',B表用'success',直接!=比永远返回“不一致”。得先建映射表或用CASE标准化 - 时间字段带时区:A表存UTC,B表存东八区,
create_time看着差8小时,其实同一时刻。要统一转成UTC再比 - 金额精度:A表存
DECIMAL(10,2),B表存INT分单位,直接比数值肯定错。得统一换算成“分”或“元” - 空字符串和NULL混用:有些系统把空状态记为
'',有些记为NULL,'' != NULL为TRUE,但业务上可能等价
当数据量大到跑不动FULL OUTER JOIN怎么办
不是优化SQL,而是换思路:用主键集合做差集,再查明细。比如先快速找出ID差异:
-- 找出A有B没有的order_id SELECT order_id FROM table_a WHERE order_id NOT IN (SELECT order_id FROM table_b WHERE order_id IS NOT NULL) UNION ALL -- 找出B有A没有的order_id SELECT order_id FROM table_b WHERE order_id NOT IN (SELECT order_id FROM table_a WHERE order_id IS NOT NULL);
再用这些ID去查具体字段。比FULL OUTER JOIN少加载大量重复字段,IO和内存压力小很多。
真正麻烦的是“两边都有但状态不同”——这一步没法绕开JOIN,只能加索引、分批次(按日期范围切片)、或导出到临时表加索引后再比。











