社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

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

脚本之家 • 6 天前 • 45 次点击  
将 脚本之家 设为“星标

第一时间收到文章更新

Joins in MySQL | Learn Top 6 Most Useful Types of Joins in MySQL

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

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

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

MySQL 连接操作的基础示例

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

创建 categories 表

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

CREATE TABLE categories (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(250NOT NULL
);

示例数据存储在 categories 表中:

id
name
1
Produce
2
Deli

创建 items 表

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

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 是连接的默认行为,因此 JOIN 和 INNER JOIN 是等价的。

内连接中的常见错误

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

示例

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

-- 除非完全确定需求,否则不要这样做:
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) 的关系。

正确示例

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

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 列,因为 items 和 categories 两个表中均有一个 name 列。为了避免混淆,可以使用 MySQL 的 别名(Alias) 功能为列指定唯一的名称。

示例:为表使用别名

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

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

提示:

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

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

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) 时,仅会返回两表中符合条件的匹配记录。示例如下:

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_id 为 NULL,在 INNER JOIN 中,它无法与 categories 表中的任何记录匹配。
  • 类别 "Dairy" 没有任何商品与其关联,因此它也不会出现在查询结果中。

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

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

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

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

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 表)中的所有记录都被包含在结果中,而左表中的数据仅在有匹配关系时才被返回。

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

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 的示例:

SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
LEFTJOIN categories c ON i.category_id = c.id
UNIONALL
SELECT
    i.id,
    i.name,
    i.category_id,
    c.name AS category_name
FROM items i
RIGHTJOIN categories c ON i.category_id = c.id
WHERE i.id ISNULL-- 仅保留未匹配的类别记录

查询结果:

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 值,例如:

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 并添加如下条件:

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 的记录:




    
SELECT * FROM (
    SELECT
        i.id,
        i.name,
        i.category_id,
        c.name AS category_name
    FROM items i
    LEFTJOIN categories c ON i.category_id = c.id
    UNIONALL
    SELECT
        i.id,
        i.name,
        i.category_id,
        c.name AS category_name
    FROM items i
    RIGHTJOIN categories c ON i.category_id = c.id
    WHERE i.id ISNULL-- 保留仅存在于右表的记录
AS all_items_all_categories
WHEREidISNULLOR category_id ISNULL;

查询结果:

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

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

总结

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

  • INNER JOIN 或 JOIN :只返回两表中键值匹配的记录。
  • LEFT JOIN :返回左表的所有记录,右表中不匹配的记录会以 NULL 补充。
  • RIGHT JOIN :返回右表的所有记录,左表中不匹配的记录会以 NULL 补充。
  • FULL OUTER JOIN :返回两表的所有记录,包括未匹配的记录(可通过 UNION 模拟实现)。
  • WHERE :可以用于过滤连接结果,只显示键值为 NULL 的记录(即未关联的记录)。
  • UNION :可合并两个查询的结果集,模拟 FULL OUTER JOIN

END

图片

  推荐阅读:
  1. 面试官:MySQL BETWEEN AND 语句包括边界吗?
  2. MySQL 一个会话占用几十 GB,你敢信?
  3. MySQL默认数据库隔离级别为什么是RR?而互联网大厂为什么把它修改为RC?
  4. 我天,MySQL 已沦为老二!

  5. 面试官:MySQL 执行计划都有哪些属性?分别是什么含义?

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/186624
 
45 次点击