在 MySQL 中,多表查询是通过 JOIN 操作实现的,用于从多个表中组合数据,根据指定的条件关联表之间的记录。多表查询的核心是使用 JOIN
关键字,结合不同的连接类型(如内连接、左连接、右连接等)来控制结果集的内容。以下详细说明 MySQL 多表查询的机制,以及内连接、左连接和右连接的用法、区别和示例。
1. 多表查询的基本概念
多表查询通过 JOIN
将多个表关联起来,基于表之间的共同列(通常是主键和外键)组合数据。MySQL 支持以下几种常见的连接类型:
- 内连接(INNER JOIN):只返回满足连接条件的记录。
- 左连接(LEFT JOIN 或 LEFT OUTER JOIN):返回左表所有记录,右表匹配的记录;无匹配时右表列为 NULL。
- 右连接(RIGHT JOIN 或 RIGHT OUTER JOIN):返回右表所有记录,左表匹配的记录;无匹配时左表列为 NULL。
- 其他连接类型(如全连接
FULL JOIN
或交叉连接CROSS JOIN
)在 MySQL 中较少使用(MySQL 不直接支持FULL JOIN
)。
基本语法:
sql
SELECT column1, column2, ...
FROM table1
[INNER | LEFT | RIGHT] JOIN table2
ON table1.column = table2.column
[WHERE condition]
[ORDER BY column];
table1
,table2
:要连接的表。ON
:指定连接条件,通常是两表之间的列关系(如table1.id = table2.id
)。WHERE
:进一步过滤结果。ORDER BY
:对结果排序。
2. 示例表结构
为说明连接类型,假设有两个表:
-
students
表(学生信息):sqlCREATE TABLE students ( student_id INT PRIMARY KEY, name VARCHAR(50), class_id INT ); INSERT INTO students (student_id, name, class_id) VALUES (1, '张三', 101), (2, '李四', 102), (3, '王五', NULL);
-
classes
表(班级信息):sqlCREATE TABLE classes ( class_id INT PRIMARY KEY, class_name VARCHAR(50) ); INSERT INTO classes (class_id, class_name) VALUES (101, '数学班'), (102, '英语班'), (103, '物理班');
表数据:
students:
student_id | name | class_id |
---|---|---|
1 | 张三 | 101 |
2 | 李四 | 102 |
3 | 王五 | NULL |
classes:
class_id | class_name |
---|---|
101 | 数学班 |
102 | 英语班 |
103 | 物理班 |
3. 内连接(INNER JOIN)
定义 :只返回两表中满足 ON
条件的记录,即两表匹配的交集部分。
语法:
sql
SELECT columns
FROM table1
INNER JOIN table2
ON table1.column = table2.column;
示例:查询学生及其所在班级名称:
sql
SELECT s.student_id, s.name, c.class_name
FROM students s
INNER JOIN classes c
ON s.class_id = c.class_id;
输出:
student_id | name | class_name |
---|---|---|
1 | 张三 | 数学班 |
2 | 李四 | 英语班 |
说明:
- 只有
students.class_id
和classes.class_id
匹配的记录才会出现在结果中。 - 王五(
class_id = NULL
)和物理班(class_id = 103
)没有匹配,因此被排除。
4. 左连接(LEFT JOIN 或 LEFT OUTER JOIN)
定义 :返回左表(table1
)的所有记录,以及右表(table2
)中匹配的记录。如果右表没有匹配记录,则返回 NULL。
语法:
sql
SELECT columns
FROM table1
LEFT JOIN table2
ON table1.column = table2.column;
示例:查询所有学生及其班级名称(即使学生没有班级):
sql
SELECT s.student_id, s.name, c.class_name
FROM students s
LEFT JOIN classes c
ON s.class_id = c.class_id;
输出:
student_id | name | class_name |
---|---|---|
1 | 张三 | 数学班 |
2 | 李四 | 英语班 |
3 | 王五 | NULL |
说明:
- 左表(
students
)的所有记录都返回。 - 王五的
class_id = NULL
,没有匹配的班级,因此class_name
为 NULL。 - 物理班(
class_id = 103
)没有匹配的学生,因此不出现。
5. 右连接(RIGHT JOIN 或 RIGHT OUTER JOIN)
定义 :返回右表(table2
)的所有记录,以及左表(table1
)中匹配的记录。如果左表没有匹配记录,则返回 NULL。
语法:
sql
SELECT columns
FROM table1
RIGHT JOIN table2
ON table1.column = table2.column;
示例:查询所有班级及其学生(即使班级没有学生):
sql
SELECT s.student_id, s.name, c.class_name
FROM students s
RIGHT JOIN classes c
ON s.class_id = c.class_id;
输出:
student_id | name | class_name |
---|---|---|
1 | 张三 | 数学班 |
2 | 李四 | 英语班 |
NULL | NULL | 物理班 |
说明:
- 右表(
classes
)的所有记录都返回。 - 物理班(
class_id = 103
)没有匹配的学生,因此student_id
和name
为 NULL。 - 王五(
class_id = NULL
)没有匹配的班级,因此不出现。
6. 多表查询的扩展
多表连接
可以连接多个表。例如,加入一个 scores
表:
sql
CREATE TABLE scores (
student_id INT,
score INT
);
INSERT INTO scores (student_id, score) VALUES
(1, 85),
(2, 92);
查询学生、班级和成绩:
sql
SELECT s.student_id, s.name, c.class_name, sc.score
FROM students s
LEFT JOIN classes c ON s.class_id = c.class_id
LEFT JOIN scores sc ON s.student_id = sc.student_id;
输出:
student_id | name | class_name | score |
---|---|---|---|
1 | 张三 | 数学班 | 85 |
2 | 李四 | 英语班 | 92 |
3 | 王五 | NULL | NULL |
结合 WHERE 和 ORDER BY
可以添加 WHERE
条件过滤和 ORDER BY
排序:
sql
SELECT s.student_id, s.name, c.class_name
FROM students s
INNER JOIN classes c
ON s.class_id = c.class_id
WHERE c.class_name = '数学班'
ORDER BY s.student_id ASC;
输出:
student_id | name | class_name |
---|---|---|
1 | 张三 | 数学班 |
7. 连接类型对比
连接类型 | 返回结果 | 使用场景 |
---|---|---|
INNER JOIN | 两表匹配的记录(交集) | 需要严格匹配数据(如学生和班级都存在) |
LEFT JOIN | 左表所有记录,右表匹配或 NULL | 确保左表数据完整(如所有学生) |
RIGHT JOIN | 右表所有记录,左表匹配或 NULL | 确保右表数据完整(如所有班级) |
8. 注意事项
-
性能 :多表查询可能影响性能,建议为连接列(如
class_id
)创建索引:sqlCREATE INDEX idx_class_id ON students(class_id);
-
NULL 值 :左连接和右连接可能返回 NULL,需用
IFNULL
或COALESCE
处理:sqlSELECT s.name, IFNULL(c.class_name, '无班级') AS class_name FROM students s LEFT JOIN classes c ON s.class_id = c.class_id;
-
多表顺序:多表连接时,连接顺序可能影响性能,尽量将小表放前面。
-
MySQL 限制 :MySQL 不支持
FULL JOIN
,可用UNION
模拟:sqlSELECT s.student_id, s.name, c.class_name FROM students s LEFT JOIN classes c ON s.class_id = c.class_id UNION SELECT s.student_id, s.name, c.class_name FROM students s RIGHT JOIN classes c ON s.class_id = c.class_id WHERE s.student_id IS NULL;
-
别名 :使用表别名(如
s
、c
)提高可读性和简洁性。
9. 实际案例
场景 :有一个 orders
表(order_id
, customer_id
, amount
)和 customers
表(customer_id
, name
)。查询所有客户的订单金额,即使某些客户没有订单:
sql
SELECT c.customer_id, c.name, o.amount
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
WHERE c.name LIKE '张%'
ORDER BY o.amount DESC;
输出(假设数据):
customer_id | name | amount |
---|---|---|
1 | 张三 | 5000 |
2 | 张伟 | NULL |
10 复杂一点的例子
这个复杂的多表连接查询示例会涉及多个表(包括内连接、左连接)、使用 WHERE
条件、聚合函数、GROUP BY
、以及 ORDER BY
等功能,并结合实际业务场景。
场景描述
假设您经营一家电商平台,数据库中有以下四个表:
- 订单表 (
订单
):记录订单信息。 - 客户表 (
客户
):记录客户信息。 - 商品表 (
商品
):记录商品信息。 - 订单详情表 (
订单详情
):记录订单中的商品明细。
我们需要查询:
- 2025年8月的所有订单信息,包括客户姓名、订单总金额、订单中的商品名称。
- 即使某些客户没有订单,也要显示客户信息。
- 按订单总金额降序排列,限制返回前5条记录。
- 计算每个订单的商品总数和总金额。
表结构和数据
以下是表结构和示例数据:
sql
-- 客户表
CREATE TABLE 客户 (
客户ID INT PRIMARY KEY,
姓名 VARCHAR(50),
城市 VARCHAR(50)
);
INSERT INTO 客户 (客户ID, 姓名, 城市) VALUES
(1, '张三', '上海'),
(2, '李四', '北京'),
(3, '王五', '广州'),
(4, '赵六', NULL);
-- 商品表
CREATE TABLE 商品 (
商品ID INT PRIMARY KEY,
商品名称 VARCHAR(100),
单价 DECIMAL(10, 2)
);
INSERT INTO 商品 (商品ID, 商品名称, 单价) VALUES
(101, '手机', 2999.99),
(102, '笔记本', 5999.99),
(103, '耳机', 199.99);
-- 订单表
CREATE TABLE 订单 (
订单ID INT PRIMARY KEY,
客户ID INT,
订单日期 DATE,
FOREIGN KEY (客户ID) REFERENCES 客户(客户ID)
);
INSERT INTO 订单 (订单ID, 客户ID, 订单日期) VALUES
(1001, 1, '2025-08-01'),
(1002, 2, '2025-08-02'),
(1003, 1, '2025-08-03'),
(1004, 3, '2025-07-30');
-- 订单详情表
CREATE TABLE 订单详情 (
订单ID INT,
商品ID INT,
数量 INT,
PRIMARY KEY (订单ID, 商品ID),
FOREIGN KEY (订单ID) REFERENCES 订单(订单ID),
FOREIGN KEY (商品ID) REFERENCES 商品(商品ID)
);
INSERT INTO 订单详情 (订单ID, 商品ID, 数量) VALUES
(1001, 101, 2),
(1001, 102, 1),
(1002, 103, 3),
(1003, 101, 1),
(1003, 103, 2);
复杂多表连接查询
以下是一个复杂的多表查询,结合左连接、内连接、聚合函数、条件过滤和排序:
sql
SELECT
c.客户ID,
c.姓名,
c.城市,
o.订单ID,
o.订单日期,
GROUP_CONCAT(p.商品名称 SEPARATOR ', ') AS 商品列表,
SUM(od.数量) AS 商品总数,
ROUND(SUM(od.数量 * p.单价), 2) AS 订单总金额
FROM 客户 c
LEFT JOIN 订单 o ON c.客户ID = o.客户ID
LEFT JOIN 订单详情 od ON o.订单ID = od.订单ID
LEFT JOIN 商品 p ON od.商品ID = p.商品ID
WHERE o.订单日期 LIKE '2025-08%'
OR o.订单日期 IS NULL
GROUP BY c.客户ID, o.订单ID
HAVING 订单总金额 > 1000 OR 订单总金额 IS NULL
ORDER BY 订单总金额 DESC
LIMIT 5;
查询说明
- 表连接 :
LEFT JOIN 订单 ON c.客户ID = o.客户ID
:确保所有客户(包括无订单的客户,如赵六)出现在结果中。LEFT JOIN 订单详情 ON o.订单ID = od.订单ID
:连接订单详情,获取订单中的商品信息。LEFT JOIN 商品 ON od.商品ID = p.商品ID
:获取商品名称和单价。
- WHERE 条件 :
o.订单日期 LIKE '2025-08%' OR o.订单日期 IS NULL
筛选2025年8月的订单,同时保留无订单的客户(订单日期 IS NULL
)。 - 聚合函数 :
GROUP_CONCAT(p.商品名称)
:将订单中的商品名称合并为逗号分隔的字符串。SUM(od.数量)
:计算订单的商品总数。SUM(od.数量 * p.单价)
:计算订单总金额。
- ROUND :
ROUND(SUM(od.数量 * p.单价), 2)
保留总金额到2位小数。 - HAVING :
订单总金额 > 1000 OR 订单总金额 IS NULL
筛选总金额大于1000的订单或无订单的客户。 - ORDER BY :按
订单总金额
降序排序。 - LIMIT:限制返回前5条记录。
输出结果
基于上述数据,查询结果如下:
客户ID | 姓名 | 城市 | 订单>ID | 订单日期 | 商品列表 | 商品总数 | 订单总金额 |
---|---|---|---|---|---|---|---|
1 | 张三 | 上海 | 1001 | 2025-08-01 | 手机, 笔记本 | 3 | 8999.97 |
1 | 张三 | 上海 | 1003 | 2025-08-03 | 手机, 耳机 | 3 | 3399.97 |
2 | 李四 | 北京 | 1002 | 2025-08-02 | 耳机 | 3 | 599.97 |
3 | 王五 | 广州 | NULL | NULL | NULL | NULL | NULL |
4 | 赵六 | NULL | NULL | NULL | NULL | NULL | NULL |