mysql不支持full outer join,需用left join+right join+union all组合实现;关键要用not exists替代not in防null陷阱,且各字段类型、顺序须严格对齐。

FULL OUTER JOIN 在 MySQL 中根本不存在——不是你写错了,是它压根没被实现。你执行 FULL OUTER JOIN,MySQL 会立刻报错 ERROR 1064。但业务上真要“两边数据全要、不匹配就补 NULL”,就得靠 LEFT JOIN、RIGHT JOIN 和 UNION ALL 自己搭。
为什么不能直接用 UNION 拼左右连接?
很多人第一反应是:LEFT JOIN 结果 + RIGHT JOIN 结果,用 UNION 合并完事。但这个做法在真实场景里容易翻车:
-
UNION会强制去重 + 排序,大表查询慢得明显,且原始重复行(比如同名员工、多条相同部门记录)会被无声抹掉 -
WHERE e.id IS NULL这类过滤只加在右连部分,前提是e.id非空;一旦左表主键允许NULL或字段本身可空,这部分就会漏数据 - 如果连接字段含
NULL(如department_id IS NULL),NOT IN子查询会整体失效——这是经典陷阱
用 UNION ALL + NOT EXISTS 才算保真
可靠写法分两块:先取所有左连结果,再单独查“右表有、左表无”的记录,并显式构造 NULL 填充左表字段。关键点在于:
- 第二段必须用
NOT EXISTS替代NOT IN,避免NULL导致整行被跳过 - 所有字段名、类型、顺序必须严格对齐,否则 MySQL 报
ERROR 1222 -
UNION ALL不去重、不排序,性能比UNION高得多,也保留原始重复
示例(以 employees 和 departments 为例):
SELECT e.id, e.name, d.id AS dept_id, d.name AS dept_name FROM employees e LEFT JOIN departments d ON e.department_id = d.id UNION ALL SELECT NULL AS id, NULL AS name, d.id AS dept_id, d.name AS dept_name FROM departments d WHERE NOT EXISTS ( SELECT 1 FROM employees e2 WHERE e2.department_id = d.id );
字段对齐和 NULL 类型必须显式声明
MySQL 对 UNION 各子查询的列类型很敏感。如果左连返回的是 INT,第二段却只写 NULL,MySQL 可能推断成 TEXT 或报错。所以:
- 每个
NULL字段都得用CAST(NULL AS TYPE)或至少加别名+显式AS声明类型倾向 - 字符串字段建议统一用
NULL AS name,数值字段用NULL AS id,避免隐式转换失败 - 若右表字段有
NOT NULL约束,但你要填NULL,MySQL 不拦——但应用层可能出空指针,得提前兜底
真正难处理的是连接条件本身含 NULL
比如 employees.department_id 允许为空,而你要把“无部门员工”和“无人部门”都纳入结果。这时候 LEFT JOIN 能带出前者,但第二段 NOT EXISTS 默认只查“有 department_id 却没匹配”的部门——那些 department_id IS NULL 的员工,其实已经包含在第一段里了。容易忽略的点是:
- 第一段
LEFT JOIN已覆盖department_id IS NULL的员工行,不用额外处理 - 第二段只负责补“右表独有”的行,逻辑上与左表是否含
NULL无关,但必须确保NOT EXISTS的子查询不因NULL条件失效 - 如果连接字段是复合条件(如
a.x = b.x AND a.y = b.y),NOT EXISTS里也要完整复现,不能只写单边
最稳妥的做法,永远是先跑通小数据集,再检查 NULL 行是否按预期分布——人眼比 SQL 更早发现漏行。











