SQL FULL JOIN 对账,怎么区分缺了一行和字段本来就是 NULL?
本文目录
面试怎么回答
我会先确保双方在同一业务粒度唯一,并对齐日期与数据时点。FULL JOIN后用明确的存在标记或非空主键识别单边缺失,再比较字段值。字段可能为空时,使用空值安全比较,区分NULL、零和空串,不能只写不等号或随意COALESCE。最后输出差异类型和样本,重复键单独治理,避免多对多关联掩盖问题。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
两边都有订单A,金额字段也都是空,FULL JOIN 对账却把它归为“目标缺失”。问题并不在 FULL JOIN,而是用右侧金额是否为 NULL 来判断那一行存不存在。一个可空的业务字段,承担不了存在性证明。
存在性和字段值是两条证据;对账先判行是否存在,再用明确的空值语义比较字段。
FULL JOIN 只保留两侧,不解释业务差异
全外关联会保留匹配行以及两边未匹配的行,但它不会自动判断“金额不一致”“状态不一致”或“目标缺单”。
更重要的前提是关联键唯一。如果左右两边同一键分别有两条和三条记录,关联可能形成六行。此时差异统计首先被连接放大,不是换一个判断条件就能修好。
因此先声明对账粒度,例如订单明细键,而不只是订单头号。重复键应单独输出,或者按业务规则汇总到约定粒度后再比较。
用存在标记,把行与值分开
如果主键有严格非空约束,可以用对侧主键是否为空识别未匹配。没有这个前提时,在两侧子查询中增加固定存在标记更清楚。
下面以PostgreSQL 17为例,假设对账键非空且双方唯一:
WITH l AS (
SELECT order_id, amount, 1 AS present FROM source_orders
), r AS (
SELECT order_id, amount, 1 AS present FROM target_orders
)
SELECT COALESCE(l.order_id, r.order_id) AS order_id,
CASE
WHEN l.present IS NULL THEN '仅目标存在'
WHEN r.present IS NULL THEN '仅源端存在'
WHEN l.amount IS DISTINCT FROM r.amount THEN '金额不同'
ELSE '一致'
END AS diff_type
FROM l FULL JOIN r ON l.order_id = r.order_id;
这里金额为空不影响行存在的判断。两边金额都为空时,是否视为一致是明示的比较规则,而不是被普通不等号悄悄漏掉。

NULL 与零,别用一个占位值糊过去
普通不等比较碰到 NULL 会得到未知,不会像真值那样直接通过 WHERE 过滤。因此只写左金额不等于右金额,会漏掉一边有值、一边为空的情况。
IS DISTINCT FROM可以按空值安全语义判断差异。不同引擎的语法不完全一致,需要使用对应能力,不能把PostgreSQL写法当成所有数据库的通用语法。
用COALESCE把空值替换为零,虽然写起来短,却主动宣布“未知金额等于真实零金额”。除非业务确认如此,否则就是改变口径;字符串中的空串和缺失值也一样。
一个典型场景:正常空值被报成缺单
比如取消订单允许金额为空,目标表已经正确写入。对账使用目标金额为空作为“目标缺失”条件,结果大量取消单被判缺失。
最初怀疑同步过滤掉了取消状态,按订单键直接查询却发现数据都在。根因是把字段缺值与整行缺失混淆。
旧验收只使用正常付费订单,金额全部非空,没有覆盖“行存在但字段为空”。修复后加入存在标记,并将缺行、字段缺值和金额不同分别展示。
同时保留两侧原值,避免差异报表只显示一个合并后的金额,让排查人员无法判断究竟是哪一边为空。
金额一致,仍然不代表整条记录一致
金额、币种、状态、业务日期和版本可能需要独立比较。不要因为金额相等,就认为订单归属也正确;金额相同的重复或错配尤其容易隐藏。
比较前应明确精度、舍入和容差。整数分计价可以精确比较;浮点指标若使用容差,要记录规则,不能用容差掩盖币种或数量级错误。
还要对齐数据快照:源端持续变化而目标存在同步延迟时,差异可能暂时合理。记录对账时点与数据成熟度,才能区分链路错误和时间差。
这类题的正确顺序是:先验证键和范围,再判断存在性,最后比较字段。每一步都留下原因,才是一份能用于修复的数据对账。
资料与适用边界
机制以所链接版本为准;业务示例用于说明判断方法,不代表作者真实项目或已验证的生产性能。