MySQL不支持FULL JOIN,需用LEFT JOIN加RIGHT JOIN再UNION ALL模拟;PostgreSQL等支持但ON不可省略;NULL是语义隔离非错误;性能差且易误用,应优先考虑替代方案。

MySQL 不支持 FULL JOIN,得用 LEFT JOIN + RIGHT JOIN 拼接
直接写 FULL JOIN 在 MySQL 里会报错:ERROR 1064: You have an error in your SQL syntax。因为 MySQL 直到 8.0.32 版本仍不原生支持 FULL JOIN(官方文档明确标注为“Not supported”)。PostgreSQL、SQL Server、Oracle 可以用,但如果你在用 MySQL——就得手动模拟。
核心思路是:用 LEFT JOIN 拿出左表全量 + 右表匹配行,再用 RIGHT JOIN 补上右表中没被左表覆盖的行,最后用 UNION ALL 合并(注意不是 UNION,避免去重开销和逻辑错乱)。
实操建议:
- 确保两张表都有明确的关联字段(比如
user_id),且类型一致;隐式转换会导致ON条件失效 -
RIGHT JOIN部分要显式写出所有字段,并把左表字段补为NULL,否则列数/顺序对不上,UNION ALL会失败 - 如果任一表有重复关联键,
UNION ALL后结果行数会膨胀,这不是 bug,是 FULL JOIN 语义本就如此
PostgreSQL / SQL Server 中 FULL JOIN 的 ON 条件不能省略
即使两表结构完全一样,也必须写 ON,不能写成 FULL JOIN other_table。省略会触发语法错误:ERROR: syntax error at or near "other_table"(PostgreSQL)或 Incorrect syntax near 'other_table'(SQL Server)。
常见错误场景是想“无条件合并所有行”,但 SQL 标准里 FULL JOIN 本质仍是基于关联逻辑的集合运算,不是 CROSS JOIN。真要无条件组合,该用 CROSS JOIN 或 UNION ALL 拆开处理。
实操建议:
- 如果确实没有自然关联字段,可临时加一列常量(如
SELECT *, 1 AS dummy FROM t1),再ON t1.dummy = t2.dummy—— 但这会让每行都匹配,等价于笛卡尔积,务必确认是否真需要 - 多个关联条件用括号包裹,比如
ON (a.id = b.user_id AND a.status = b.status),避免因 AND/OR 优先级导致意外空值
FULL JOIN 结果里的 NULL 值不是数据缺失,而是语义隔离
比如 users 表有 id=5,但 orders 表没对应订单,那这一行的 orders.order_id、orders.amount 全是 NULL。这不是查询出错了,是 FULL JOIN 明确告诉你的:“这条记录只存在于左表”。同理,右表独有行的左表字段也是 NULL。
容易踩的坑:
- 后续用
WHERE order_id IS NOT NULL过滤时,会把左表独有行干掉——这其实退化成了INNER JOIN,不是你想要的“全量” - 聚合函数如
COUNT(*)会统计所有行,但COUNT(order_id)会忽略NULL,结果比预期少;需用COUNT(*)或COUNT(1)统计总行数 - 如果用
COALESCE(users.name, orders.customer_name)做名称兜底,要小心两边都为NULL的情况,可能需要嵌套判断
大数据量下 FULL JOIN 性能差,先考虑是否真需要
FULL JOIN 无法利用索引跳过不匹配行,数据库必须扫描两张表并构建哈希表或排序归并,内存和 IO 开销远高于 INNER JOIN。100 万行 × 100 万行的组合,中间结果可能达 TB 级别。
更务实的做法:
- 先用
SELECT COUNT(*)分别查两张表行数,再用SELECT COUNT(*) FROM t1 FULL JOIN t2 ON ...预估结果规模,避免跑挂 - 如果只是想查“哪些 ID 在 A 表有、B 表没有”,用
NOT EXISTS或LEFT JOIN ... WHERE b.id IS NULL,比FULL JOIN快一个数量级 - 导出场景下,与其在数据库里硬算
FULL JOIN,不如用 Python/Pandas 分别读取再merge(how='outer'),可控性更强
真正需要 FULL JOIN 的场景其实很少,多数时候是需求没拆解清楚——比如“对比两套系统用户清单差异”,重点其实是差集,不是拼成一张宽表。










