404 Not Found

404 Not Found


nginx

连接类型详解

连接类型详解

想象你经营一家咖啡店,每天要处理三种情况:既有会员又消费了的顾客(两表都有数据)、有会员但没消费的顾客(左表有、右表没有)、消费了但没注册会员的顾客(右表有、左表没有)。SQL提供了不同类型的JOIN来精确处理这三种(以及更多)场景。


1. 核心概念

六种连接类型总览

连接类型 说明 返回结果
INNER JOIN 内连接 只返回两表都匹配的行
LEFT JOIN 左连接 返回左表所有行 + 右表匹配的行(不匹配则为NULL)
RIGHT JOIN 右连接 返回右表所有行 + 左表匹配的行(不匹配则为NULL)
FULL OUTER JOIN 全外连接 返回两表所有行(不匹配则为NULL)
CROSS JOIN 交叉连接 返回两表的笛卡尔积(所有组合)
SELF JOIN 自连接 表与自身连接

用韦恩图理解JOIN

用两个圆圈A和B表示两张表:

TEXT
    ┌───────┐   ┌───────┐
    │   A   │   │   B   │
    │       │   │       │
    │   ┌───┼───┼───┐   │
    │   │ A ∩ B │   │   │
    │   └───┼───┼───┘   │
    │       │   │       │
    └───────┘   └───────┘

    INNER JOIN  → A ∩ B(交集)
    LEFT JOIN   → A(全部A + A∩B)
    RIGHT JOIN  → B(全部B + A∩B)
    FULL JOIN   → A ∪ B(并集)
    CROSS JOIN  → A × B(所有组合)

INNER JOIN(内连接)

只保留两表中都匹配的行。上一课已详细介绍,此处不再赘述。

SQL
SELECT e.name, d.name AS department
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;

LEFT JOIN(左连接)

返回左表所有行,右表没有匹配的列显示为NULL。左表是FROM后面的第一张表。

SQL
SELECT e.name, d.name AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;

输出(注意周九的department为NULL):

TEXT
name    department
------  ----------
张三    技术部
李四    技术部
王五    市场部
赵六    财务部
钱七    技术部
孙八    市场部
周九    NULL
吴十    财务部
💡 提示:LEFT JOIN是最常用的连接类型之一,特别适合"查找没有关联数据的记录",比如"没有部门的员工"。

RIGHT JOIN(右连接)

返回右表所有行,左表没有匹配的列显示为NULL。右表是JOIN后面那张表。

SQL
SELECT e.name, d.name AS department
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id;
⚠️ 注意:SQLite不支持RIGHT JOIN。如果需要RIGHT JOIN的效果,可以将两张表位置互换,改用LEFT JOIN实现:

SQL
-- 等价于 RIGHT JOIN departments d ON ...
SELECT e.name, d.name AS department
FROM departments d
LEFT JOIN employees e ON e.department_id = d.id;

FULL OUTER JOIN(全外连接)

返回两表所有行,没有匹配的地方显示为NULL。相当于LEFT JOIN和RIGHT JOIN的并集。

SQL
-- 语法(MySQL/PostgreSQL/SQL Server支持)
SELECT e.name, d.name AS department
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.id;
⚠️ SQLite不支持FULL OUTER JOIN,但可以用LEFT JOIN + UNION + RIGHT JOIN来模拟:

SQL
-- 用 LEFT JOIN UNION RIGHT JOIN 模拟 FULL OUTER JOIN
SELECT e.name, d.name AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
UNION
SELECT e.name, d.name AS department
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id;

同样需要注意SQLite不支持RIGHT JOIN,实际在SQLite中可以这样实现:

SQL
-- SQLite中的FULL OUTER JOIN等价写法
SELECT e.name, d.name AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
UNION
SELECT e.name, d.name AS department
FROM departments d
LEFT JOIN employees e ON e.department_id = d.id;

💡 提示:实际开发中,FULL OUTER JOIN使用频率较低。大多数场景用LEFT JOIN就能满足需求,建议优先考虑LEFT JOIN。

CROSS JOIN(交叉连接)

返回两表的笛卡尔积——左表每一行与右表每一行的所有组合。结果行数 = 左表行数 × 右表行数。

SQL
SELECT e.name, d.name AS department
FROM employees e
CROSS JOIN departments d;

假设employees有8行、departments有4行,结果将有 8 × 4 = 32 行。

💡 提示:CROSS JOIN通常用于生成组合矩阵(如"每个员工 × 每个季度"的报表模板)。使用时要小心数据量爆炸。

SELF JOIN(自连接)

表与自身连接。常用于查询"层级关系"或"同一表中的配对",比如员工和他们的上级。

SQL
-- 假设employees表有一个manager_id字段
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
💡 提示:自连接的表必须用不同的别名来区分(如em),否则SQL无法区分两次引用。

JOIN选择决策树

面对业务需求时,按以下逻辑选择连接类型:

TEXT
需要连接两张表?
├── 只要匹配的数据 → INNER JOIN
├── 保留左表全部数据 → LEFT JOIN
├── 保留右表全部数据 → RIGHT JOIN
├── 保留两表全部数据 → FULL OUTER JOIN
├── 需要所有组合 → CROSS JOIN
└── 表与自身关联 → SELF JOIN

2. 基本语法/用法

LEFT JOIN 语法

SQL
SELECT 列名
FROM 表A
LEFT JOIN 表B ON 表A.列 = 表B.列;
💡 提示LEFT OUTER JOINLEFT JOIN是完全等价的,OUTER关键字可以省略。

💡 提示:LEFT JOIN中,ON条件只影响右表的匹配,左表的所有行都会被保留。

RIGHT JOIN 语法

SQL
SELECT 列名
FROM 表A
RIGHT JOIN 表B ON 表A.列 = 表B.列;

CROSS JOIN 语法

SQL
SELECT 列名
FROM 表A
CROSS JOIN 表B;
-- CROSS JOIN 不需要 ON 条件

SELF JOIN 语法

SQL
SELECT a.列, b.列
FROM 表A a
JOIN 表A b ON a.某列 = b.某列;
💡 提示:自连接的关键是用别名把同一张表"变成"两张表来操作。


示例:查找没有部门的员工(难度⭐)

用LEFT JOIN找出所有员工,包括那些没有分配部门的:

SQL
SELECT e.name AS employee, 
       COALESCE(d.name, '未分配') AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
▶ 试一试

输出:

TEXT
employee  department
--------  ----------
张三      技术部
李四      技术部
王五      市场部
赵六      财务部
钱七      技术部
孙八      市场部
周九      未分配
吴十      财务部

COALESCE(d.name, '未分配')表示:如果d.name是NULL,就显示"未分配"。


示例:查找没有订单的商品(难度⭐⭐)

商品表中有8个商品,但不是每个商品都有订单。用LEFT JOIN找出"零销量"的商品:

SQL
SELECT p.name AS product, p.category, p.price,
       COUNT(o.id) AS order_count
FROM products p
LEFT JOIN orders o ON p.id = o.product_id
GROUP BY p.id, p.name, p.category, p.price
HAVING COUNT(o.id) = 0;
▶ 试一试

输出(取决于数据,某些商品可能没有订单记录):

TEXT
product  category  price     order_count
-------  --------  --------  -----------
Mac Mini 电脑      4499.00   0
💡 思路:先用LEFT JOIN把所有商品和订单关联,再用GROUP BY聚合,最后用HAVING过滤出订单数为0的商品。


示例:员工与上级的自连接(难度⭐⭐⭐)

假设employees表增加了manager_id字段,查询每个员工及其上级:

SQL
-- 先添加manager_id字段和数据
ALTER TABLE employees ADD COLUMN manager_id INTEGER;

UPDATE employees SET manager_id = 5 WHERE id IN (1, 2, 3, 6);
UPDATE employees SET manager_id = NULL WHERE id = 5;

-- 自连接查询
SELECT e.name AS employee, 
       COALESCE(m.name, '无上级') AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
▶ 试一试

输出:

TEXT
employee  manager
--------  --------
张三      钱七
李四      钱七
王五      钱七
赵六      无上级
钱七      无上级
孙八      钱七
周九      无上级
吴十      无上级
💡 思路:自连接时,同一张表扮演两个角色——e代表"作为员工的自己",m代表"作为上级的自己"。通过manager_id = m.id建立层级关系。


3. 常见应用场景

场景一:生成部门-员工矩阵报表

用CROSS JOIN生成"每个部门 × 每个员工"的完整矩阵:

SQL
SELECT d.name AS department, e.name AS employee
FROM departments d
CROSS JOIN employees e
WHERE e.department_id IS NOT NULL
ORDER BY d.name, e.name;

这在生成"全员参与的打卡表"等场景中很实用。

场景二:查找同部门的员工配对

用SELF JOIN找出同一部门的所有员工配对:

SQL
SELECT e1.name AS employee1, 
       e2.name AS employee2, 
       d.name AS department
FROM employees e1
INNER JOIN employees e2 ON e1.department_id = e2.department_id 
    AND e1.id < e2.id
INNER JOIN departments d ON e1.department_id = d.id;

输出:

TEXT
employee1  employee2  department
---------  ---------  ----------
张三       李四       技术部
张三       钱七       技术部
李四       钱七       技术部
王五       孙八       市场部
赵六       吴十       财务部
💡 关键e1.id < e2.id避免了重复配对(如"张三-李四"和"李四-张三"只保留一个)和自配对(如"张三-张三")。


❓ 常见问题

Q:LEFT JOIN和INNER JOIN什么时候结果一样? A: 当左表中没有NULL的外键值时(即所有行都能在右表中找到匹配),LEFT JOIN和INNER JOIN的结果完全一致。但LEFT JOIN会多保留那些右表没有匹配的行。

Q:SQLite不支持RIGHT JOIN和FULL OUTER JOIN怎么办? A: RIGHT JOIN可以通过调换表的顺序改用LEFT JOIN实现。FULL OUTER JOIN可以用LEFT JOIN + UNION + LEFT JOIN模拟。实际上RIGHT JOIN在实际开发中也很少使用,大多数情况LEFT JOIN就够了。

Q:CROSS JOIN会不会很慢? A: 如果两张表分别有1000行,CROSS JOIN会产生100万行结果。所以使用时要非常小心,确保数据量可控。通常CROSS JOIN会配合WHERE条件使用,实际上等价于INNER JOIN。

Q:SELF JOIN有什么实际用途? A: SELF JOIN常用于处理层级数据(如组织架构、评论回复、分类树),也用于查找"同组内的配对"(如同部门员工、同城市客户)。只要需要"同一张表内的行与行之间做比较",就可能用到SELF JOIN。


📖 小节


📝 作业

作业1(⭐):编写查询,显示所有部门及其员工数量,包括没有员工的部门。结果按员工数量降序排列。

作业2(⭐⭐):编写查询,找出所有"没有下过订单的客户"。假设我们需要一个customers表,可以先用orders表中的customer_name字段模拟——找出哪些客户只出现在orders表中,哪些商品从未被订购过。

作业3(⭐⭐⭐):编写查询,用SELF JOIN找出"入职日期相邻的员工对"——即入职日期最接近的两位员工(提示:可以用julianday()函数计算日期差)。


下一课

👉 09-subquery - 子查询:学习在WHERE、FROM、SELECT中使用子查询,以及EXISTS/NOT EXISTS的用法!

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏