LEFT JOIN 是什么
LEFT JOIN 左外连接以左表为基准,保留左表的全部行,右表没有匹配时用 NULL 补齐右表那几列。它解决的是主表加可选关联信息这类查询:用户列表带最近一次登录、订单带可选绑定的优惠券。写外连接时,属于右表的过滤条件必须放在 ON 里;放在 WHERE 里会把补 NULL 出来的行过滤掉,查询结果静默退化成内连接,少了数据也不报错。
基本信息
| 作用 | 保留左表全部行,右表缺匹配补 NULL |
|---|---|
| 语法 | SELECT … FROM a LEFT JOIN b ON a.id = b.a_id |
| 等价写法 | LEFT OUTER JOIN,OUTER 可省略 |
| 右表过滤 | 条件写 ON;写 WHERE 会过滤掉补 NULL 的行 |
| 行数下限 | 不小于左表行数;左表一行可被放大成多行 |
| 适用平台 | 标准 SQL,MySQL 8.0 与 PostgreSQL 均支持 |
典型使用场景
- 主表带可选关联:用户列表显示最近一次登录时间,没有登录记录也保留用户行
- 找缺失数据:找出从未下单的用户,配合 WHERE 右表主键 IS NULL
- 报表补零:日期维度表左连接事实表,没有数据的日期保留为 0 而不是整行消失
- 软关联:订单可选绑定优惠券、可选绑定推荐人
- 多表串联:A 左连 B 再左连 C,每一步都可能补出新的 NULL,需要逐段验证
- 数据比对:新旧系统用户表左连接,找出只在旧系统存在的账号
基础示例
基础示例
SELECT u.id,
u.name,
o.order_no, # 没有订单时为 NULL
o.amount
FROM users AS u # 左表:全部用户
LEFT JOIN orders AS o ON o.user_id = u.id # 连接条件写在 ON
ORDER BY u.id;找未匹配与错误写法
找未匹配与错误写法
-- 正确:左连接再加右表主键 IS NULL
SELECT u.id FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;
-- 等价:NOT EXISTS,语义不受 NULL 影响
SELECT u.id FROM users u
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- 错误示范:右表条件写进 WHERE,没有订单的用户被过滤掉
SELECT u.id, o.order_no
FROM users u LEFT JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid';条件位置的差异
| 条件放哪 | 对左表未匹配行的影响 | 结果 |
|---|---|---|
| ON 里加右表条件 | 左表行仍保留,右表列补 NULL | 仍是外连接,只是收窄匹配范围 |
| WHERE 里加右表条件 | 补 NULL 的行条件为 UNKNOWN 被丢弃 | 退化为内连接 |
| WHERE 里加左表条件 | 只过滤左表行,不改变连接语义 | 可以安全使用 |
| WHERE 右表列 IS NULL | 专门筛出未匹配行 | 反连接的常见写法 |
把右表过滤条件写在 WHERE 是外连接最高频的错误,结果集静默变成内连接,数据少了却不报错,排查时要对照两边行数。
判断是否匹配要用右表主键或非空列做 IS NULL(例如 o.id IS NULL),用可能为空的业务列判断会把真实存在的行也当成未匹配。
左连接会把左表行放大:左表一行匹配右表多行时结果行数增加,直接 SUM 会重复计数;先在子查询里把右表聚合到一行再连接。
多级左连接时中间表补出的 NULL 会继续向下传递,条件越多越难定位;建议每连一张表就单独跑一次看行数,确认没有意外放大。
先把右表聚合到一行
先把右表聚合到一行
-- 左连接明细表会放大行数,先按用户聚合再连接
SELECT u.id, u.name, t.order_cnt, t.total_amount
FROM users AS u
LEFT JOIN (
SELECT user_id, # 按用户聚合
COUNT(*) AS order_cnt,
SUM(amount) AS total_amount
FROM orders
WHERE status = 'paid' # 过滤放在子查询里,语义更清楚
GROUP BY user_id
) AS t ON t.user_id = u.id; # 连接粒度是用户,不会放大写法要点
- 左连接后接右连接的过滤,条件一律放 ON,除非确实想过滤掉未匹配行
- 用 LEFT JOIN 再对右表列做 IS NULL,是反连接的经典写法,但可读性不如 NOT EXISTS
- 需要区分未匹配与匹配但值为空时,加一个布尔列:o.id IS NOT NULL AS has_order
- MySQL 不支持 FULL JOIN,需要 FULL 语义时用 LEFT JOIN 与 RIGHT JOIN 的结果做 UNION
- ON 与 WHERE 里的条件顺序不影响结果,但把连接条件与过滤条件分开写可读性明显更好
- 外连接的结果行数先算出来再聚合,能避免把重复计数问题带到报表口径里