FULL OUTER JOIN结果中大量NULL并非重复,而是分别保留左表独有行(右字段为NULL)和右表独有行(左字段为NULL)所致;视觉上易误判,实为两组独立缺失。

为什么FULL OUTER JOIN结果里一堆NULL还看起来像重复?
这不是“重复”,而是FULL OUTER JOIN的正常行为:它必须同时保留左表和右表的所有行,不匹配的部分用NULL填充。你看到的“大量NULL”,其实是两组独立的缺失——左表有、右表无;或右表有、左表无。它们在结果中物理上是不同行,但因大量字段为NULL,视觉上容易误判为“重复记录”。
FULL OUTER JOIN后行数暴增,但不是笛卡尔积导致的
和CROSS JOIN不同,FULL OUTER JOIN本身不会产生笛卡尔积式膨胀。但如果ON条件宽松(比如只靠status = 'active'而没绑定主键)、或某张表存在重复关联键(如t_user_tag里一个user_id对应5条标签),那匹配过程仍会触发一对多展开——此时左表1行 + 右表5行 → 结果出现5行,其中右表字段有值、左表其余字段为NULL的组合也会出现5次。
- 检查方式:
SELECT COUNT(*) FROM table_a FULL OUTER JOIN table_b ON ...对比两张表原始行数之和;若远大于该和,说明ON条件没收敛 - 典型陷阱:用非唯一列(如
name)做JOIN键,同名用户/商品引发交叉匹配 - MySQL不原生支持
FULL OUTER JOIN,靠LEFT JOIN+RIGHT JOIN+UNION ALL模拟时,若没加WHERE筛出NULL侧,会把两边的NULL行重复包含
WHERE子句一加,FULL OUTER JOIN就“变味”了
写WHERE b.category IS NOT NULL会直接过滤掉所有右表不匹配的行,等价于退化成LEFT JOIN;同理WHERE a.id IS NULL会只留右表孤儿数据。真正想保留全集时,过滤逻辑必须挪到ON子句里,或用子查询预处理。
- 错误写法:
FULL OUTER JOIN b ON a.id = b.user_id WHERE b.status = 'valid'→ 实际丢弃了所有右表不满足status的行,包括本该作为“右表孤儿”保留的记录 - 正确写法:把条件放进ON:
FULL OUTER JOIN b ON a.id = b.user_id AND b.status = 'valid' - 更安全的做法:先对右表用子查询
GROUP BY或ROW_NUMBER()收敛粒度,再JOIN
用UNION ALL模拟FULL OUTER JOIN时,NULL字段对不齐
当数据库不支持原生FULL OUTER JOIN(如MySQL 5.7),常用LEFT JOIN + RIGHT JOIN + UNION ALL实现。但极易出错:两张子查询的列顺序、别名、数据类型稍有不一致,UNION ALL就会让NULL填错列,导致某列全是NULL、另一列数据错位,看起来像“重复又错乱”。
- 必须显式写出所有列,不能用
SELECT *:例如SELECT a.id, a.name, b.order_id, b.amount FROM ...和SELECT NULL AS id, NULL AS name, b.order_id, b.amount FROM ... - 注意字符串和数值型
NULL的隐式转换风险,某些引擎会把NULL转成空字符串或0 -
UNION ALL不自动去重,若左右两边恰好有完全相同的行(极少见),会真实重复——这时才需要DISTINCT,但应放在最外层且明确知道代价
最容易被忽略的是:FULL OUTER JOIN的结果集,既不是左表的副本,也不是右表的副本,而是一个全新结构的数据集。一旦你把它当成主表继续JOIN第三张表,或者直接GROUP BY某个字段统计,那些看似“整齐”的NULL列会立刻干扰聚合逻辑——比如COUNT(b.order_id)漏算左表孤儿,AVG()跳过所有NULL却仍按总行数分母计算。

















