FULL JOIN 不能直接用于自动对账,因为它仅拼接数据、不识别差异类型;真正对账需在其基础上用 CASE 标注状态、COALESCE 处理空值、HAVING 过滤匹配项,并解决凭证标准化、多行拆分、期间对齐三大坑。

FULL JOIN 不能直接用于自动对账——它只做数据拼接,不判断差异类型,盲目用会把错账当“已核销”。
为什么 FULL JOIN 本身不是对账逻辑
对账的核心是识别「仅A有」「仅B有」「A和B都有但金额不等」三类问题,而 FULL JOIN 只负责把左右表记录按关联字段拉通,ON 条件匹配不上时补 NULL,它不比较数值、不标记状态、不聚合差异。常见误用是写成:
SELECT * FROM ledger_a FULL JOIN ledger_b ON a.voucher_no = b.voucher_no;
结果里一堆 NULL 字段,但你无法一眼看出哪条是漏记、哪条是金额错误、哪条是重复入账。
真正可用的对账 SQL 结构:用 FULL JOIN 搭配 CASE 和聚合
必须把 FULL JOIN 当作“底座”,再用条件判断填充业务语义。关键点:
-
COALESCE(a.voucher_no, b.voucher_no)作为统一凭证号,避免 NULL 干扰分组 - 用
CASE明确标注状态:'MISSING_IN_A'/'MISSING_IN_B'/'AMOUNT_MISMATCH'/'MATCHED' - 对金额做
COALESCE(a.amount, 0)和COALESCE(b.amount, 0),防止 NULL 参与运算导致整行失效 - 加
HAVING过滤掉完全匹配的行(否则结果太冗长)
示例(简化版):
SELECT<br> COALESCE(a.voucher_no, b.voucher_no) AS voucher_no,<br> CASE<br> WHEN a.voucher_no IS NULL THEN 'MISSING_IN_A'<br> WHEN b.voucher_no IS NULL THEN 'MISSING_IN_B'<br> WHEN ABS(COALESCE(a.amount, 0) - COALESCE(b.amount, 0)) > 0.01 THEN 'AMOUNT_MISMATCH'<br> ELSE 'MATCHED'<br> END AS status,<br> COALESCE(a.amount, 0) AS amount_a,<br> COALESCE(b.amount, 0) AS amount_b<br>FROM ledger_a a<br>FULL JOIN ledger_b b ON a.voucher_no = b.voucher_no<br>HAVING status != 'MATCHED';
实际对账必须处理的 3 个坑
真实账目远比示例复杂,这几个点不处理,SQL 跑出来全是假阳性:
-
voucher_no可能跨系统格式不一致(如带前缀/大小写/空格),得先用TRIM(UPPER())标准化再JOIN - 同一笔业务在两套账中可能拆成多行(如税金分离),不能只靠凭证号硬匹配,得加
GROUP BY+SUM()汇总后比对 - 时间范围不一致(如A账结到 2024-05-31,B账只到 2024-05-28),必须显式加
WHERE限定相同会计期间,否则FULL JOIN会把“未达账项”误判为差异
替代方案:什么时候该放弃 FULL JOIN?
当账目量大(单表超百万)、或需支持逐笔追溯、或要对接下游预警系统时,FULL JOIN 的一次性快照模式就力不从心了。此时更稳的做法是:
- 用
UNION ALL把两套账合并成一张“带来源标签”的宽表,再用窗口函数ROW_NUMBER() OVER (PARTITION BY voucher_no ORDER BY source)找出单边记录 - 把对账逻辑下推到应用层(Python/Java),用哈希表做内存比对,便于插桩调试、加日志、接告警
- 对高频对账场景,提前建好物化视图或汇总表,避免每次全表
FULL JOIN
对账不是拼数据,是验证一致性。FULL JOIN 是把锤子,但你得知道钉子在哪、钉多深、会不会敲歪——尤其是金额精度、凭证标准化、期间对齐这三点,漏一个,结果就不可信。

















