一篇文章全面重温MySQL的join操作

一篇文章全面重温MySQL的join操作

关系型数据库(例如 MySQL)能够将数据存储在多个独立的表中,同时支持在需要时通过关系将这些表连接起来。

MySQL 提供了强大的连接操作(Join)功能,可以高效地将多个表中的数据关联在一起。通过连接操作,可以将多个表中的列合并到一个结果集,实现比执行多次查询并手动合并结果更高效的数据整合。

本文将详细介绍 MySQL 中支持的各种连接操作类型,以及如何使用这些操作来组合不同表中的数据,包括内连接(Inner Join)、左连接(Left Join)、右连接(Right Join)和完全外连接(Full Outer Join)。

MySQL 连接操作的基础示例

为了更直观地理解连接操作,我们将构建一个简单的示例数据库,存储杂货商品及其所属的类别。类别信息存储在 categories 表中,商品信息存储在单独的 items 表中,两个表之间通过关联字段建立关系。

创建 categories

以下是 SQL 语句用于创建 categories 表:

sql 复制代码
CREATE TABLE categories (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(250) NOT NULL
);

示例数据存储在 categories 表中:

id name
1 Produce
2 Deli
创建 items

以下是 SQL 语句用于创建 items 表:

sql 复制代码
CREATE TABLE items (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(512),
  category_id INT NULL
);

示例数据存储在 items 表中:

id name category_id
1 Apples 1
2 Cheese 2

后续实践案例将基于上述示例表来探讨各种类型的连接操作及如何使用它们来查询和整合数据。

内连接(Inner Join)

在存储了商品 (items) 和类别 (categories) 的数据后,我们希望查询时能够显示每个商品对应的类别名称,而不仅仅是 category_id。例如,对用户来说,显示「Deli」比显示数字「2」更具有可读性和意义。

为达到这个目的,我们可以使用 内连接(Inner Join) ,它会返回两个表中符合指定关联条件的匹配记录。需要注意的是,INNER JOIN 是连接的默认行为,因此 JOININNER JOIN 是等价的。

内连接中的常见错误

如果在执行连接时未指定 ON 条件,MySQL 会执行一种特殊的连接,称为交叉连接(CROSS JOIN) 。交叉连接会将左表中的每一行与右表中的每一行进行匹配组合,这通常不是我们想要的结果,而且可能产生远超预期数量的数据。

示例

以下是一个未指定 ON 条件的查询示例:

sql 复制代码
-- 除非完全确定需求,否则不要这样做:
SELECT * FROM items
JOIN categories; -- 没有指定 ON 条件!

查询结果可能如下:

id name category_id id name
1 Apples 1 1 Produce
1 Apples 1 2 Deli
2 Cheese 2 1 Produce
2 Cheese 2 2 Deli

可以看出,左表的每一行都与右表的每一行进行了组合。如果左表有 m 条记录,右表有 n 条记录,那么最终结果将包含 m * n 条记录。这种行为在大数据集下可能引发性能问题,并且通常不符合用户的需求。

使用 ON 指定连接的列

为了获得所需的结果,必须明确指定两个表之间的关联列。换句话说,我们需要定义 categories 表的主键 (id) 与 items 表的外键 (category_id) 的关系。

正确示例

以下是一个正确的内连接查询示例:

vbnet 复制代码
SELECT * FROM items
JOIN categories ON items.category_id = categories.id; -- 显式指定 ON 条件

查询结果:

id name category_id id name
1 Apples 1 1 Produce
2 Cheese 2 2 Deli

通过明确指定 ON 条件,我们成功将商品的数据与它们的类别名称关联起来。

给列指定唯一名称

你可能注意到上面的结果中有两个 name 列,因为 itemscategories 两个表中均有一个 name 列。为了避免混淆,可以使用 MySQL 的 别名(Alias) 功能为列指定唯一的名称。

示例:为表使用别名

以下是一个使用别名的查询示例,其中 items 表被简化为 icategories 表被简化为 c

vbnet 复制代码
SELECT * FROM items AS i -- 为 items 表指定别名 i
JOIN categories AS c     -- 为 categories 表指定别名 c
    ON i.category_id = c.id;

提示:

  • AS 是可选的,通常可以省略。
  • 不建议直接使用 * 选择所有列,特别是在表中包含许多列的情况下。明确指定所需的列既可以提高查询效率,也可以使结果更简洁清晰。

以下是优化后的查询,仅返回所需列并为列指定唯一名称:

vbnet 复制代码
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name -- 为 categories.name 指定别名 category_name
FROM items AS i
JOIN categories AS c ON i.category_id = c.id;

查询结果:

id name category_id category_name
1 Apples 1 Produce
2 Cheese 2 Deli

通过为列指定唯一名称,可以得到更清晰、实用的查询结果。

左连接(Left Join)和右连接(Right Join)

假设我们在数据库中添加了以下新数据:

  • 一个没有类别的商品。
  • 一个新类别(但暂时没有商品与之关联)。

更新后的 categories 表数据如下:

id name
1 Produce
2 Deli
3 Dairy

更新后的 items 表数据如下:

id name category_id
1 Apples 1
2 Cheese 2
3 Bread NULL

内连接(INNER JOIN):只返回匹配记录

当我们对此数据执行 内连接(INNER JOIN) 时,仅会返回两表中符合条件的匹配记录。示例如下:

css 复制代码
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
JOIN categories c ON i.category_id = c.id;

查询结果:

id name category_id category_name
1 Apples 1 Produce
2 Cheese 2 Deli

什么数据缺失了?

仔细观察结果,会发现商品 "Bread" 和类别 "Dairy" 并没有被返回。这是因为:

  • 商品 "Bread" 的 category_idNULL,在 INNER JOIN 中,它无法与 categories 表中的任何记录匹配。
  • 类别 "Dairy" 没有任何商品与其关联,因此它也不会出现在查询结果中。

左连接(Left Join):返回所有左表记录

在某些场景中,我们希望返回左表(items)中的所有记录,即使这些记录没有与右表中任何记录关联。

为实现这一需求,可以使用左连接(Left Join) 。左连接会确保左表中的所有数据被包含在结果中,而右表中的数据仅在有匹配时才被返回。

执行左连接的查询示例如下:

css 复制代码
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
LEFT JOIN categories c ON i.category_id = c.id;

查询结果:

id name category_id category_name
1 Apples 1 Produce
2 Cheese 2 Deli
3 Bread NULL NULL

上述查询,使用左连接,返回了所有商品记录,包括没有类别的商品 "Bread"。

右连接(Right Join):返回所有右表记录

右连接(RIGHT JOIN)和左连接的工作方式类似,只是查询方向相反。右连接会确保右表(在本例中为 categories 表)中的所有记录都被包含在结果中,而左表中的数据仅在有匹配关系时才被返回。

执行右连接的查询示例如下:

css 复制代码
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
RIGHT JOIN categories c ON i.category_id = c.id;

查询结果:

id name category_id category_name
1 Apples 1 Produce
2 Cheese 2 Deli
NULL NULL NULL Dairy

上述查询,使用右连接,返回了所有类别的记录,包括没有商品关联的 "Dairy" 类别。

完整外连接(Full Outer Join):返回所有记录

在某些情况下,我们需要同时查询所有商品所有类别 ,无论它们之间是否存在关联。这可以通过完整外连接(Full Outer Join) 实现。

然而,值得注意的是,MySQL 并不直接支持 FULL OUTER JOIN。但我们可以通过结合左连接(Left Join)右连接(Right Join) ,并使用 UNION 合并结果集来模拟这种行为。

模拟 FULL OUTER JOIN 的查询

以下是通过使用 UNION 模拟 FULL OUTER JOIN 的示例:

css 复制代码
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
LEFT JOIN categories c ON i.category_id = c.id
UNION ALL
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
RIGHT JOIN categories c ON i.category_id = c.id
WHERE i.id IS NULL; -- 仅保留未匹配的类别记录

查询结果:

id name category_id category_name
1 Apples 1 Produce
2 Cheese 2 Deli
3 Bread NULL NULL
NULL NULL NULL Dairy

通过该查询,我们得到了一个完整的数据集,既包含没有关联类别的商品,也包含没有关联商品的类别。

筛选未关联的数据(WHERE 键值为 NULL)

有时候,我们的需求是仅查询没有关联记录的项。例如,找出哪些商品没有分类,或者哪些类别没有商品关联,以便清理或补充数据。

查询未分类的商品

要查询没有类别的商品,可以使用 LEFT JOIN 并添加 WHERE 条件筛选 NULL 值,例如:

css 复制代码
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
LEFT JOIN categories c ON i.category_id = c.id
WHERE c.id IS NULL;

查询结果:

id name category_id category_name
3 Bread NULL NULL

该查询返回了商品 "Bread",它当前没有关联的类别。

查询无关联商品的类别

同样,如果需要找出没有商品的类别,可以使用 RIGHT JOIN 并添加如下条件:

css 复制代码
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
RIGHT JOIN categories c ON i.category_id = c.id
WHERE i.id IS NULL;

查询结果:

id name category_id category_name
NULL NULL NULL Dairy

该查询返回了类别 "Dairy",它当前没有商品与之关联。

完整外连接:仅显示未关联的数据

如果我们希望仅查询 未关联的商品未关联的类别 ,可以基于 FULL OUTER JOIN 的模拟查询,进一步添加 WHERE 条件,仅保留键值为 NULL 的记录:

sql 复制代码
SELECT * FROM (
    SELECT
        i.id,
        i.name,
        i.category_id,
        c.name AS category_name
    FROM items i
    LEFT JOIN categories c ON i.category_id = c.id
    UNION ALL
    SELECT
        i.id,
        i.name,
        i.category_id,
        c.name AS category_name
    FROM items i
    RIGHT JOIN categories c ON i.category_id = c.id
    WHERE i.id IS NULL -- 保留仅存在于右表的记录
) AS all_items_all_categories
WHERE id IS NULL OR category_id IS NULL;

查询结果:

id name category_id category_name
3 Bread NULL NULL
NULL NULL NULL Dairy

通过聚合筛选条件,查询结果仅保留未关联的商品和未关联的类别。

总结

理解 MySQL 中不同类型的连接操作可以帮助我们更好地处理多表数据。这些连接类型各有适用场景,以下是它们的区别总结:

  • INNER JOINJOIN:只返回两表中键值匹配的记录。
  • LEFT JOIN :返回左表的所有记录,右表中不匹配的记录会以 NULL 补充。
  • RIGHT JOIN :返回右表的所有记录,左表中不匹配的记录会以 NULL 补充。
  • FULL OUTER JOIN :返回两表的所有记录,包括未匹配的记录(可通过 UNION 模拟实现)。
  • WHERE :可以用于过滤连接结果,只显示键值为 NULL 的记录(即未关联的记录)。
  • UNION :可合并两个查询的结果集,模拟 FULL OUTER JOIN
相关推荐
卿雪4 小时前
Redis 线程模型:Redis为什么这么快?Redis为什么引入多线程?
java·数据库·redis·sql·mysql·缓存·golang
梁萌4 小时前
MySQL中innerDB引擎的锁机制
数据库·mysql·索引·表锁·行锁
Chloeis Syntax4 小时前
MySQL初阶学习日记(4)--- 插入、聚合、分组查询 + 数据库约束
数据库·笔记·学习·mysql
西岭千秋雪_5 小时前
MySQL集群搭建
java·数据库·分布式·mysql
古城小栈5 小时前
MySQL 配置优化 绿皮书
数据库·mysql
箬敏伊儿6 小时前
Apple M2 + Docker + MySQL 轻量配置全教程
数据库·mysql·docker
FserSuN6 小时前
mysql8 loose index skip scan 特性加速分组查询性能
数据库·mysql
tebukaopu1487 小时前
mysql distinct慢
数据库·mysql
l1t7 小时前
利用Duckdb求解Advent of Code 2025第5题 自助餐厅
数据库·sql·mysql·算法·oracle·duckdb·advent of code
豐儀麟阁贵9 小时前
9.6使用正则表达式
java·开发语言·数据库·mysql