MySQL多表查询 #

一、连接概述 #

1.1 为什么需要多表查询 #

text
┌─────────────┐     ┌─────────────┐
│   users     │     │   orders    │
├─────────────┤     ├─────────────┤
│ id          │←────│ user_id     │
│ name        │     │ id          │
│ email       │     │ amount      │
└─────────────┘     │ order_date  │
                    └─────────────┘

-- 查询用户及其订单信息需要连接两个表

1.2 连接类型 #

连接类型 说明
INNER JOIN 内连接,返回匹配的行
LEFT JOIN 左连接,返回左表所有行
RIGHT JOIN 右连接,返回右表所有行
CROSS JOIN 交叉连接,笛卡尔积
NATURAL JOIN 自然连接,自动匹配同名列

1.3 示例表结构 #

sql
-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    department_id INT
);

INSERT INTO users VALUES
    (1, 'John', 1),
    (2, 'Jane', 1),
    (3, 'Bob', 2),
    (4, 'Alice', NULL);

-- 部门表
CREATE TABLE departments (
    id INT PRIMARY KEY,
    name VARCHAR(50)
);

INSERT INTO departments VALUES
    (1, 'Engineering'),
    (2, 'Marketing'),
    (3, 'Sales');

-- 订单表
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2),
    order_date DATE
);

INSERT INTO orders VALUES
    (1, 1, 100.00, '2024-01-15'),
    (2, 1, 200.00, '2024-01-16'),
    (3, 2, 150.00, '2024-01-17'),
    (4, 5, 300.00, '2024-01-18');

二、内连接(INNER JOIN) #

2.1 基本语法 #

sql
SELECT columns
FROM table1
INNER JOIN table2 ON table1.column = table2.column;

-- INNER可以省略
SELECT columns
FROM table1
JOIN table2 ON table1.column = table2.column;

2.2 基本示例 #

sql
-- 查询用户及其部门
SELECT u.name, d.name AS department
FROM users u
INNER JOIN departments d ON u.department_id = d.id;

+------+-------------+
| name | department  |
+------+-------------+
| John | Engineering |
| Jane | Engineering |
| Bob  | Marketing   |
+------+-------------+

-- 注意:Alice没有部门,不会出现在结果中

2.3 多表内连接 #

sql
-- 三表连接
SELECT u.name, d.name AS department, o.amount
FROM users u
INNER JOIN departments d ON u.department_id = d.id
INNER JOIN orders o ON u.id = o.user_id;

+------+-------------+--------+
| name | department  | amount |
+------+-------------+--------+
| John | Engineering | 100.00 |
| John | Engineering | 200.00 |
| Jane | Engineering | 150.00 |
+------+-------------+--------+

2.4 使用USING #

sql
-- 当连接列名相同时,可以使用USING
SELECT u.name, d.name AS department
FROM users u
INNER JOIN departments d USING (id);

-- 等价于
SELECT u.name, d.name AS department
FROM users u
INNER JOIN departments d ON u.id = d.id;

三、左连接(LEFT JOIN) #

3.1 基本语法 #

sql
SELECT columns
FROM table1
LEFT JOIN table2 ON table1.column = table2.column;

3.2 基本示例 #

sql
-- 查询所有用户及其部门(包括没有部门的用户)
SELECT u.name, d.name AS department
FROM users u
LEFT JOIN departments d ON u.department_id = d.id;

+-------+-------------+
| name  | department  |
+-------+-------------+
| John  | Engineering |
| Jane  | Engineering |
| Bob   | Marketing   |
| Alice | NULL        |
+-------+-------------+

-- Alice没有部门,但仍然出现在结果中

3.3 查找不匹配的行 #

sql
-- 查找没有部门的用户
SELECT u.name
FROM users u
LEFT JOIN departments d ON u.department_id = d.id
WHERE d.id IS NULL;

+-------+
| name  |
+-------+
| Alice |
+-------+

3.4 左连接与COUNT #

sql
-- 统计每个部门的用户数(包括没有用户的部门)
SELECT d.name, COUNT(u.id) AS user_count
FROM departments d
LEFT JOIN users u ON d.id = u.department_id
GROUP BY d.id;

-- 注意:使用COUNT(u.id)而不是COUNT(*),避免NULL被计数

四、右连接(RIGHT JOIN) #

4.1 基本语法 #

sql
SELECT columns
FROM table1
RIGHT JOIN table2 ON table1.column = table2.column;

4.2 基本示例 #

sql
-- 查询所有部门及其用户(包括没有用户的部门)
SELECT u.name, d.name AS department
FROM users u
RIGHT JOIN departments d ON u.department_id = d.id;

+------+-------------+
| name | department  |
+------+-------------+
| John | Engineering |
| Jane | Engineering |
| Bob  | Marketing   |
| NULL | Sales       |
+------+-------------+

-- Sales部门没有用户,但仍然出现在结果中

4.3 右连接转左连接 #

sql
-- 右连接可以转换为左连接(推荐使用左连接)
SELECT u.name, d.name AS department
FROM departments d
LEFT JOIN users u ON d.id = u.department_id;

五、全外连接 #

5.1 MySQL实现全外连接 #

sql
-- MySQL不直接支持FULL OUTER JOIN
-- 使用UNION实现
SELECT u.name, d.name AS department
FROM users u
LEFT JOIN departments d ON u.department_id = d.id
UNION
SELECT u.name, d.name AS department
FROM users u
RIGHT JOIN departments d ON u.department_id = d.id;

+-------+-------------+
| name  | department  |
+-------+-------------+
| John  | Engineering |
| Jane  | Engineering |
| Bob   | Marketing   |
| Alice | NULL        |
| NULL  | Sales       |
+-------+-------------+

六、交叉连接(CROSS JOIN) #

6.1 基本语法 #

sql
SELECT columns
FROM table1
CROSS JOIN table2;

-- 等价于
SELECT columns
FROM table1, table2;

6.2 基本示例 #

sql
-- 生成所有组合
SELECT u.name AS user, d.name AS department
FROM users u
CROSS JOIN departments d;

-- 4个用户 × 3个部门 = 12行结果
+-------+-------------+
| user  | department  |
+-------+-------------+
| John  | Engineering |
| John  | Marketing   |
| John  | Sales       |
| Jane  | Engineering |
| ...   | ...         |
+-------+-------------+

6.3 使用场景 #

sql
-- 生成日期和产品的所有组合
SELECT d.date, p.product_name
FROM dates d
CROSS JOIN products p;

-- 生成报表模板

七、自连接 #

7.1 基本概念 #

sql
-- 自连接:表与自身连接
-- 需要使用别名区分

7.2 示例:员工与经理 #

sql
-- 员工表(包含经理ID)
CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    manager_id INT
);

INSERT INTO employees VALUES
    (1, 'CEO', NULL),
    (2, 'Manager1', 1),
    (3, 'Manager2', 1),
    (4, 'Employee1', 2),
    (5, 'Employee2', 3);

-- 查询员工及其经理
SELECT 
    e.name AS employee,
    m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

+-----------+----------+
| employee  | manager  |
+-----------+----------+
| CEO       | NULL     |
| Manager1  | CEO      |
| Manager2  | CEO      |
| Employee1 | Manager1 |
| Employee2 | Manager2 |
+-----------+----------+

7.3 示例:查找同一部门的用户 #

sql
-- 查找同一部门的用户对
SELECT u1.name, u2.name, u1.department_id
FROM users u1
JOIN users u2 ON u1.department_id = u2.department_id
WHERE u1.id < u2.id;

八、自然连接(NATURAL JOIN) #

8.1 基本语法 #

sql
SELECT columns
FROM table1
NATURAL JOIN table2;

8.2 基本示例 #

sql
-- 自动匹配同名列
SELECT * FROM users
NATURAL JOIN departments;

-- 等价于
SELECT * FROM users
INNER JOIN departments USING (id);

8.3 注意事项 #

sql
-- 不推荐使用NATURAL JOIN
-- 原因:
-- 1. 不明确连接条件
-- 2. 如果表结构变化可能导致意外结果
-- 3. 难以调试和维护

-- 推荐:明确指定连接条件
SELECT * FROM users u
INNER JOIN departments d ON u.department_id = d.id;

九、连接条件详解 #

9.1 ON与WHERE区别 #

sql
-- ON:连接条件,决定如何连接
-- WHERE:过滤条件,在连接后过滤

-- 左连接中使用ON
SELECT u.name, d.name AS department
FROM users u
LEFT JOIN departments d ON u.department_id = d.id AND d.name = 'Engineering';

-- 左连接中使用WHERE
SELECT u.name, d.name AS department
FROM users u
LEFT JOIN departments d ON u.department_id = d.id
WHERE d.name = 'Engineering';

-- 区别:
-- ON中的条件不会过滤左表
-- WHERE中的条件会过滤结果

9.2 复杂连接条件 #

sql
-- 多条件连接
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
    AND o.order_date >= '2024-01-01'
    AND o.amount > 100;

-- 使用函数
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
    AND YEAR(o.order_date) = 2024;

十、连接性能优化 #

10.1 索引优化 #

sql
-- 确保连接列有索引
CREATE INDEX idx_users_department ON users(department_id);
CREATE INDEX idx_orders_user ON orders(user_id);

-- 查看执行计划
EXPLAIN SELECT u.name, d.name
FROM users u
JOIN departments d ON u.department_id = d.id;

10.2 小表驱动大表 #

sql
-- 让小表作为驱动表
-- MySQL优化器通常会自动选择

-- 手动指定(使用STRAIGHT_JOIN)
SELECT STRAIGHT_JOIN u.name, d.name
FROM small_table u
JOIN large_table d ON u.id = d.user_id;

10.3 避免笛卡尔积 #

sql
-- 错误:忘记连接条件
SELECT * FROM users, departments;  -- 笛卡尔积

-- 正确:添加连接条件
SELECT * FROM users u
JOIN departments d ON u.department_id = d.id;

十一、复杂连接示例 #

11.1 多表连接查询 #

sql
-- 查询用户、部门、订单信息
SELECT 
    u.name AS user_name,
    d.name AS department_name,
    o.amount,
    o.order_date
FROM users u
LEFT JOIN departments d ON u.department_id = d.id
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100 OR o.amount IS NULL;

11.2 分组统计 #

sql
-- 统计每个部门的订单总额
SELECT 
    d.name AS department,
    COUNT(o.id) AS order_count,
    COALESCE(SUM(o.amount), 0) AS total_amount
FROM departments d
LEFT JOIN users u ON d.id = u.department_id
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY d.id
ORDER BY total_amount DESC;

11.3 层级查询 #

sql
-- 使用自连接查询组织架构
SELECT 
    e.name AS employee,
    m.name AS manager,
    mm.name AS manager_manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id
LEFT JOIN employees mm ON m.manager_id = mm.id;

十二、总结 #

连接类型对比:

连接类型 说明 结果
INNER JOIN 内连接 只返回匹配行
LEFT JOIN 左连接 返回左表所有行
RIGHT JOIN 右连接 返回右表所有行
CROSS JOIN 交叉连接 笛卡尔积
SELF JOIN 自连接 表与自身连接

最佳实践:

  1. 优先使用LEFT JOIN,语义更清晰
  2. 确保连接列有索引
  3. 明确指定连接条件,避免NATURAL JOIN
  4. 注意ON和WHERE的区别
  5. 使用EXPLAIN分析查询计划

下一步,让我们学习索引!

最后更新:2026-03-26