FULL OUTER JOIN能查出两表差异,因其返回左右表所有行,不匹配字段填充NULL,从而暴露“仅左有”或“仅右有”的数据;需配合WHERE筛选a.id IS NULL或b.id IS NULL才能准确定位差异行。

为什么FULL OUTER JOIN能查出两表差异
FULL OUTER JOIN 会保留左表和右表的所有行,匹配不上的用 NULL 填充。这恰好暴露了“只在左表有”“只在右表有”的数据——也就是差异本身。但要注意:它不会自动过滤出差异行,必须配合 WHERE 显式筛选 NULL 字段。
常见错误是写成 SELECT * FROM a FULL OUTER JOIN b ON a.id = b.id 后就停手,结果看到大量匹配行混在中间,根本没法一眼定位差异。
关键点在于:差异行一定出现在连接键(比如主键)一侧为 NULL 的位置。所以真正要抓的是 a.id IS NULL 或 b.id IS NULL 的行。
如何精准筛选左表有、右表无的行(即右表主键为NULL)
这是典型的“仅存在于左表”的场景,对应业务如:订单表里有记录,但对账表里没生成对账单。
写法必须带 WHERE 条件,且注意 IS NULL 判断的对象是右表的连接字段:
-
WHERE b.id IS NULL—— 正确,表示右表没匹配到 -
WHERE b.id = NULL—— 错误,= NULL永远返回UNKNOWN,查不到任何行 - 如果连接条件含多个字段(如
ON a.order_id = b.order_id AND a.version = b.version),则需检查所有右表字段是否都为NULL,例如WHERE b.order_id IS NULL AND b.version IS NULL
SELECT 'only_in_orders' AS source, o.* FROM orders o FULL OUTER JOIN reconciliation r ON o.id = r.order_id WHERE r.order_id IS NULL;
如何同时查出两端缺失的行并打标区分
实际排查时往往需要一次性看到“只在A”“只在B”两类差异,避免来回切换查询。
最稳妥的方式是用 CASE + WHERE 组合,而不是试图用 UNION 拼接——后者容易因字段顺序或类型隐式转换出错,且执行计划可能更差。
- 用
CASE WHEN a.id IS NULL THEN 'only_in_b' WHEN b.id IS NULL THEN 'only_in_a' END打标签 -
WHERE a.id IS NULL OR b.id IS NULL确保只取差异行(否则FULL OUTER JOIN会把匹配行也拉进来) - 注意:若某表主键允许
NULL(极不推荐),此逻辑会误判;务必确认主键定义为NOT NULL
SELECT
CASE
WHEN a.id IS NULL THEN 'only_in_b'
WHEN b.id IS NULL THEN 'only_in_a'
END AS diff_type,
COALESCE(a.id, b.id) AS id,
a.name AS a_name, b.name AS b_name
FROM table_a a
FULL OUTER JOIN table_b b ON a.id = b.id
WHERE a.id IS NULL OR b.id IS NULL;MySQL用户怎么办:它不支持FULL OUTER JOIN
MySQL 8.0 仍不原生支持 FULL OUTER JOIN,强行用会导致语法错误 ERROR 1064。
替代方案不是硬套 LEFT JOIN + RIGHT JOIN + UNION,而是用 UNION ALL 模拟,且必须去重逻辑要小心:
- 先
SELECT ... FROM a LEFT JOIN b ON ... WHERE b.id IS NULL - 再
SELECT ... FROM b LEFT JOIN a ON ... WHERE a.id IS NULL - 用
UNION ALL(不是UNION)拼接,避免隐式去重开销 - 若两表结构不同,需对齐字段数和类型,用
NULL AS col_name补位
性能上,两次 LEFT JOIN 比一次 FULL OUTER JOIN 多扫一次表,大数据量时延迟明显。真要高频对比,建议导出主键集用外部工具(如 diff 或 Python 集合运算)处理。
主键为 NULL 的行本身在规范设计中不应存在;所谓“筛选主键为 NULL 的行”,本质是筛选连接失败后被填充的占位 NULL,不是查原始数据里的空值。这点混淆会导致条件写反或漏判。

















