NULL = NULL在SQL中不成立,导致FULL JOIN无法匹配NULL值,需用IS NOT DISTINCT FROM或COALESCE规避;WHERE过滤右表NULL会使FULL JOIN退化为INNER JOIN;聚合时应区分NULL来源,避免误补0。

ON条件里NULL = NULL永远不成立,FULL JOIN也救不了
FULL JOIN确实会保留两边所有行,但它的“匹配逻辑”依然依赖ON条件的布尔判断。而NULL = NULL在SQL三值逻辑中返回UNKNOWN,不是TRUE,所以不会触发任何行之间的连接——哪怕左表和右表都有work_type IS NULL的记录,它们也不会被连到一起。
结果就是:这些本该“对应”的NULL行,在结果集中各自独立存在,左表那行右表字段全为NULL,右表那行左表字段全为NULL,看起来像“两边都缺”,实则业务上可能是同一类未标注数据。报表一汇总,就误判成“两边各丢了N条”,而不是“这N条根本没对上”。
- 排查方法:先单独查
SELECT COUNT(*) FROM table_a WHERE join_col IS NULL和SELECT COUNT(*) FROM table_b WHERE join_col IS NULL,再对比FULL JOIN结果里table_a.join_col IS NULL AND table_b.join_col IS NULL的行数——正常应为0 - 真正想让NULL之间也能匹配,得用
IS NOT DISTINCT FROM(PostgreSQL/SQL:2003标准),或退化写法:COALESCE(a.join_col, -999) = COALESCE(b.join_col, -999)(前提是-999在业务中绝不会出现)
FULL JOIN + COALESCE容易掩盖“谁该为NULL负责”
很多人一看到FULL JOIN结果里一堆NULL,马上套COALESCE填默认值,比如COALESCE(a.name, b.name, '未知')。这看似解决了显示问题,却抹掉了NULL的原始归属:到底是左表没这条,还是右表没这条,还是两边都没?
业务层看到“未知”,就默认是数据缺失;但实际可能是A系统漏同步、B系统多写了脏数据、或两边都写了但key不一致——三种原因对应三种修复路径,而COALESCE把它们全压成一个字符串,后续没人再追问来源。
- 更安全的做法:保留原始字段,加一列标注来源,例如
CASE WHEN a.id IS NOT NULL AND b.id IS NULL THEN '仅A' WHEN a.id IS NULL AND b.id IS NOT NULL THEN '仅B' ELSE '双方都有' END AS source_flag - 如果必须用
COALESCE,优先按业务权重排序参数,比如COALESCE(a.name, b.name)隐含“以A系统为准”,别写成COALESCE(b.name, a.name)再不加注释
WHERE里过滤NULL会让FULL JOIN当场退化成INNER JOIN
写SELECT * FROM a FULL JOIN b ON a.id = b.id WHERE b.status = 'active',表面看是“查全量再筛状态”,实际执行时,b.status = 'active'会把所有b行为NULL的行全干掉——因为NULL = 'active'是UNKNOWN,不满足WHERE条件。
结果就是:左表那些没匹配到b的记录,全部消失,FULL JOIN变成事实上的INNER JOIN,还带一个误导性假象:“我用了FULL,肯定没丢数据”。
- 正确做法:把右表业务条件挪进ON,写成
ON a.id = b.id AND b.status = 'active',这样没匹配或状态不符的b字段仍为NULL,但a行保留 - 左表条件可以放心放WHERE,比如
WHERE a.created_at >= '2026-01-01',它只影响a行存留,不破坏FULL语义
聚合函数在FULL JOIN结果上直接用会静默丢数据
SUM(coupon_amount)遇到NULL自动跳过,这本身没错;但放在FULL JOIN结果里,问题在于:你根本分不清这个NULL是“右表真没这条记录”,还是“右表有记录但coupon_amount字段本身就是NULL”。两种情况业务含义不同,但聚合时都被当成“无值”忽略。
比如财务要算“所有订单的总优惠金额”,未用券订单的coupon_amount是NULL,但口径要求计为0;而因数据同步失败导致右表整行缺失的订单,coupon_amount也是NULL,但这部分本不该计入统计。可SUM(COALESCE(coupon_amount, 0))把两者全补0,等于把错误数据也当有效数据加了进去。
- 关键区分点:先用
CASE WHEN b.id IS NULL THEN 'missing' ELSE 'present' END标记数据完整性,再决定是否补0 - 涉及算术运算时,别只补一个字段,比如
total - discount,得写成COALESCE(total, 0) - COALESCE(discount, 0),否则任一为NULL整行变NULL
业务上最危险的不是NULL本身,而是FULL JOIN让你误以为“我已经看见全部”,其实你只看见了被三值逻辑切碎又拼错的碎片。处理前先问一句:这些NULL,到底是谁没写,还是谁没读,还是根本没传?

















