核心要点

  • INNER JOIN 只保留两表匹配成功的行;LEFT/RIGHT JOIN 保留左/右表全部行,未匹配侧补 NULL

  • FULL OUTER JOIN 取并集(双方都保留,未匹配补 NULL);CROSS JOIN 是笛卡尔积,不带 ON

  • 关联键有重复值会导致行数「放大」:m 条对 n 条会产生 m×n 行,统计前需先去重或聚合

  • 易错点:LEFT JOIN 后把对右表字段的过滤条件写进 WHERE,会把它变回 INNER JOIN,应写进 ON

标准回答

JOIN 的本质是按 ON 条件把两张表的行配对。INNER JOIN 只输出双方都匹配的行;LEFT JOIN 以左表为基准保留全部左表行、右表缺失补 NULL,RIGHT 反之;FULL OUTER JOIN 取两侧并集;CROSS JOIN 不带条件做笛卡尔积。实务中两大坑:一是关联键重复会让结果行数成倍放大,需先聚合去重;二是 LEFT JOIN 时若把右表过滤条件放进 WHERE,NULL 行会被滤掉退化成 INNER JOIN,正确做法是放进 ON。

sql
-- 查询每个用户的订单数,没有订单的用户也要显示(订单数为 0)
SELECT
  u.user_id,
  u.user_name,
  COUNT(o.order_id) AS order_cnt   -- COUNT 字段会忽略 NULL,无订单计为 0
FROM users u
LEFT JOIN orders o
  ON u.user_id = o.user_id
  AND o.status = 'paid'            -- 过滤条件放 ON,保留无订单用户
GROUP BY u.user_id, u.user_name
ORDER BY order_cnt DESC;

常见误区

⚠️ 常见踩坑

LEFT JOIN 时把右表条件写进 WHERE(如 WHERE o.status='paid')会过滤掉补 NULL 的行,使外连接退化为内连接;统计 COUNT 时也要用 COUNT(右表字段) 而非 COUNT(*),否则无匹配行会被错误计为 1。

追问

追问 1如何只查出在 A 表存在但在 B 表不存在的记录(反连接)?

用 LEFT JOIN + IS NULL:SELECT a.* FROM A LEFT JOIN B ON a.key=b.key WHERE b.key IS NULL,匹配不上的右表字段为 NULL 即为差集。也可用 NOT EXISTS(推荐,对 NULL 更安全)或 NOT IN(要注意子查询含 NULL 会导致结果全空的坑)。

追问 2为什么 JOIN 后行数比预期多很多?

通常是关联键在某一侧(或两侧)有重复值,发生了行放大:左表 1 条匹配右表 3 条就变 3 行。排查办法是先对各表的关联键做 COUNT(*) 与 COUNT(DISTINCT key) 对比看是否一对多;修复办法是在 JOIN 前对右表按键聚合去重,或确认业务上确实是一对多再做后续 SUM/COUNT。

延伸学习

与本题相关的知识库文章、术语、工具与行业资讯。