极简工具箱

免费在线工具 · 本地计算 · 无需上传

SQL 的几种 JOIN:INNER、LEFT 与「左表全都要」

`LEFT JOIN` 之后加了个 `WHERE b.status = 1`,左表的数据突然少了——因为 WHERE 把「没匹配上」的行过滤掉了,左连接退化成了内连接。

广告位(百度联盟)

一、五种 JOIN 的集合含义

类型结果
INNER JOIN两边都匹配上的行
LEFT JOIN左表全部 + 右表匹配的(无匹配补 NULL)
RIGHT JOIN右表全部 + 左表匹配的
FULL JOIN两边全部(MySQL 不直接支持,用 UNION 模拟)
CROSS JOIN笛卡尔积,行数 = 左 × 右
SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;

结果包含「没有订单的用户」,o.amount 为 NULL。

二、ON 与 WHERE 的关键区别

这是最容易出错的地方:

-- A:条件在 ON 里 → 左表全部保留,右表只匹配 status=1 的
SELECT * FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status = 1;

-- B:条件在 WHERE 里 → 先连接,再过滤,没订单的行被 NULL 判断筛掉
SELECT * FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.status = 1;

B 的结果里没有「没有订单的用户」,因为 NULL = 1 不成立。想让左表全部保留,右表的过滤条件必须写在 ON 里。

反过来,如果确实想「只要有订单的用户」,那 B 是对的——但这时用 INNER JOIN 表达意图更清楚。

三、判断 NULL 的坑

-- 错误:NULL = NULL 不成立,永远查不到
WHERE o.status = NULL

-- 正确
WHERE o.status IS NULL

左连接后想筛「没有匹配的」,标准写法是:

SELECT u.* FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;

四、驱动表与索引

MySQL 的 JOIN 是「嵌套循环」:拿驱动表的每一行,去被驱动表里找匹配。所以:

-- 确保 o.user_id 有索引
ALTER TABLE orders ADD INDEX idx_user (user_id);

EXPLAIN 里第一行通常是驱动表;出现 Using join buffer 说明被驱动表没走索引。

五、笛卡尔积事故

漏写 ON 条件:

SELECT * FROM users, orders;   -- 10 万 × 100 万 = 1000 亿行

查询会直接把数据库拖死。防护:

六、多表关联的书写顺序

建议按「主表 → 维度表 → 明细表」的顺序:

SELECT u.name, o.id, i.product_name, i.qty
FROM orders o
JOIN users u        ON u.id = o.user_id
JOIN order_items i  ON i.order_id = o.id
WHERE o.created_at >= '2026-11-01'

ON 紧跟各自的 JOIN,不要把所有条件堆在最后。这样 review 时容易发现漏条件。

七、性能优化要点

-- 先取一页 ID,再回表关联
SELECT u.name, o.amount
FROM (SELECT id, user_id, amount FROM orders ORDER BY id DESC LIMIT 20) o
JOIN users u ON u.id = o.user_id;

小结

LEFT JOIN 想保留左表全部,右表条件写 ON 而非 WHERE;判断空用 IS NULL;被驱动表的关联字段必须有索引;别漏 ON,笛卡尔积能直接把库打挂。

本文相关工具(本地计算,无需上传)

更多使用教程