left join与right join本质对称可互换,建议统一用left join;on中误写where条件会使外连接退化为内连接;mysql不支持full outer join,需用left+right+union模拟;聚合函数默认忽略null,需用coalesce处理。

LEFT JOIN 和 RIGHT JOIN 本质是同一个东西
SQL 没有“真正的 RIGHT JOIN”,它只是 LEFT JOIN 的镜像写法,语义完全可互换。多数人用 LEFT JOIN 是因为阅读顺序更符合直觉:左表是主表,右表是补充数据。但如果你把两张表位置调换,RIGHT JOIN 就等价于 LEFT JOIN —— 不是语法糖,是数学对称性决定的。
实操建议:
- 统一用
LEFT JOIN,避免团队里有人写左、有人写右,导致逻辑翻转难排查 - 别在复杂多表连接中混用
LEFT和RIGHT,容易搞错驱动表(即哪张表保留所有行) -
RIGHT JOIN在 PostgreSQL 和 SQL Server 中支持,在 SQLite 中不支持,MySQL 5.7+ 支持但解析器会悄悄转成LEFT JOIN
ON 条件里写 WHERE 条件会把 OUTER 变成 INNER
这是最常踩的坑:LEFT JOIN ... ON a.id = b.a_id WHERE b.status = 'active' 看似想查“所有 a,附带 active 的 b”,实际效果是只返回那些有匹配且 status 为 active 的 a 行 —— 因为 WHERE 是在连接完成后过滤,把 b 为 NULL 的行全干掉了。
正确做法是把过滤条件挪进 ON:
SELECT a.*, b.name FROM users a LEFT JOIN orders b ON a.id = b.user_id AND b.status = 'active';
这样,即使某个用户没有 active 订单,b.name 仍是 NULL,但该用户仍会被保留。
常见错误现象:
- 本该返回 100 行,结果只出来 23 行,且全是关联成功的记录
- 执行计划里显示
Hash Join后紧跟Filter,说明 WHERE 已介入连接逻辑 - 用 EXPLAIN 查看时,发现
rows数远小于左表总行数,暗示 OUTER 失效
FULL OUTER JOIN 在 MySQL 中根本不存在
MySQL 直到 8.0.32 都不支持 FULL OUTER JOIN。你写上去会报错:ERROR 1064 (42000): You have an error in your SQL syntax。这不是版本问题,是设计取舍 —— 官方认为可用 LEFT JOIN + UNION + RIGHT JOIN 组合模拟。
替代写法(需去重,注意 NULL 匹配):
SELECT a.id, a.name, b.amount FROM users a LEFT JOIN orders b ON a.id = b.user_id UNION ALL SELECT NULL, NULL, b.amount FROM orders b WHERE b.user_id NOT IN (SELECT id FROM users WHERE id IS NOT NULL);
但要注意:
-
UNION ALL比UNION快,前提是业务能容忍重复(比如两边都无匹配的 NULL 行不会出现两次) -
NOT IN遇到NULL会整个失效,必须加WHERE id IS NOT NULL - PostgreSQL 和 SQL Server 原生支持
FULL OUTER JOIN,语法一致,无需改写
JOIN 后的 NULL 值处理不当会导致聚合出错
比如写 SELECT COUNT(b.id) FROM users a LEFT JOIN orders b ON a.id = b.user_id,你以为在统计“每个用户下的订单数”,其实是在统计“非 NULL 的 b.id 总数”——这没问题;但换成 SUM(b.amount),就会忽略所有 b 为 NULL 的行,结果比预期小,而且不会报错、没提示。
关键点:
-
COUNT(*)统计所有行,COUNT(b.id)只统计 b.id 非 NULL 行 -
SUM、AVG、MAX等聚合函数默认跳过 NULL,不是 bug,是标准行为 - 如果要体现“无订单用户金额为 0”,得显式用
COALESCE(b.amount, 0) - 在 GROUP BY 场景下,NULL 分组可能被合并或丢失,尤其当
GROUP BY b.category时,b 为 NULL 的行会单独成一组,容易被忽略
真正麻烦的是,这些 NULL 行在开发环境数据少时看不出问题,上线后数据量一大,统计偏差就暴露了 —— 而且很难回溯到底是 JOIN 写法、聚合逻辑,还是原始数据缺值导致的。










