MySQL 多表查询的应用

在 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 表(学生信息):

    sql 复制代码
    CREATE 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 表(班级信息):

    sql 复制代码
    CREATE 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_idclasses.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_idname 为 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)创建索引:

    sql 复制代码
    CREATE INDEX idx_class_id ON students(class_id);
  • NULL 值 :左连接和右连接可能返回 NULL,需用 IFNULLCOALESCE 处理:

    sql 复制代码
    SELECT 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 模拟:

    sql 复制代码
    SELECT 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;
  • 别名 :使用表别名(如 sc)提高可读性和简洁性。

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 等功能,并结合实际业务场景。

场景描述

假设您经营一家电商平台,数据库中有以下四个表:

  1. 订单表 (订单):记录订单信息。
  2. 客户表 (客户):记录客户信息。
  3. 商品表 (商品):记录商品信息。
  4. 订单详情表 (订单详情):记录订单中的商品明细。

我们需要查询:

  • 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.单价):计算订单总金额。
  • ROUNDROUND(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
相关推荐
携欢2 小时前
Portswigger靶场之 Blind SQL injection with time delays通关秘籍
数据库·sql
十八旬2 小时前
苍穹外卖项目实战(日记十)-记录实战教程及问题的解决方法-(day3-2)新增菜品功能完整版
java·开发语言·spring boot·mysql·idea·苍穹外卖
FeBaby2 小时前
mysql为什么使用b+树不使用红黑树
数据库·b树·mysql
ZHZCE3 小时前
在 Ubuntu 20.04 上安装 MySQL 8.0
mysql
令狐少侠20113 小时前
如何使用navicat连接容器中的mysql数据库
mysql·docker
青草地溪水旁3 小时前
`mysql_query()` 数据库查询函数
数据库·mysql·c
秦jh_4 小时前
【MySQL】基本查询
linux·数据库·c++·mysql
一只叫煤球的猫14 小时前
看到同事设计的表结构我人麻了!聊聊怎么更好去设计数据库表
后端·mysql·面试
遇见你的雩风15 小时前
【MySQL】CRUD基础详解
数据库·mysql