MySQL 零基础教程

MySQL INNER JOIN

关系型数据库中的数据通常分散在多个表中,以保持效率并避免冗余(正如我们在模块 2 和模块 6 第一课中讨论数据库设计和主/外键时所探讨的那样)。为了检索结合了这些相关表中数据的有意义信息,我们使用 JOIN (连接) 操作。

INNER JOIN(内连接)是最基本、最常用的 JOIN 类型之一,用于根据两个或多个表之间的相关列合并它们的数据行。它仅返回在基于指定的连接条件在两个表中都有匹配项的行。

1. 理解 INNER JOIN

只要两个表的列之间存在匹配,INNER JOIN 关键字就会从两个表中选择所有行。当一个公共字段(或一组字段)具有匹配的值时,它有效地将两个表中的行组合在一起。在另一个表中没有对应匹配项的行将被排除在结果集之外。

考虑我们一直在使用的 world 数据库。它有一个 city(城市)表和一个 country(国家)表。每个城市都属于一个特定的国家。city 表有一个 CountryCode 列,这是一个引用 country 表中 Code 列(主键)的外键。INNER JOIN 允许我们在单个结果集中合并来自两个表的信息,例如城市名称及其对应的国家名称。

INNER JOIN 的基本语法是:

SELECT
    column_list
FROM
    table1
INNER JOIN
    table2 ON table1.matching_column = table2.matching_column;
  • SELECT column_list: 指定你希望从连接后的表中检索的列。
  • FROM table1: 指示从中检索数据的第一个表。
  • INNER JOIN table2: 指定要与 table1 连接的第二个表。
  • ON table1.matching_column = table2.matching_column: 这是连接条件 (join condition)。它指定了两个表之间的关系。INNER JOIN 将仅返回 table1.matching_column 中的值等于 table2.matching_column 中的值的行。

使用别名 (aliases) 来表示表名是一种常见的做法,这可以使查询更简短、更易读,尤其是在处理多个表或较长的表名时。

SELECT
    c.Name AS CityName,
    co.Name AS CountryName
FROM
    city AS c
INNER JOIN
    country AS co ON c.CountryCode = co.Code;

在这里,ccity 表的别名,cocountry 表的别名。通过缩短列引用的长度,这极大地提高了代码的可读性。

2. INNER JOIN 的详细示例

让我们使用 world 数据库,通过几个实际例子来说明 INNER JOIN

2.1 示例 1:列出城市及其所属国家

要查看所有城市及其所属国家的列表,我们可以基于它们共同的 CountryCode/Code 列连接 citycountry 表。

SELECT
    city.Name AS City,
    country.Name AS Country
FROM
    city
INNER JOIN
    country ON city.CountryCode = country.Code
ORDER BY
    country.Name, city.Name;

在这个查询中:

  1. 我们选择 city 表中的 Name(别名为 City)和 country 表中的 Name(别名为 Country)。
  2. 我们将 citycountry 连接起来。
  3. ON 子句指定 city 表中的 CountryCode 必须匹配 country 表中的 Code
  4. ORDER BY 用于先按国家、再按城市进行字母排序。

结果集将仅包含在 country 表中具有匹配国家代码的城市。如果由于某种原因,city 表中的某个城市具有在 country 表中不存在的 CountryCode,那么该城市将不会出现在结果中。

2.2 示例 2:组合三个表

INNER JOIN 可以扩展为组合两个以上的表。我们只需链接多个 INNER JOIN 子句。假设我们想要列出所有城市、它们的国家以及该国家所在的大洲。由于 country 表已经有了一个 Continent 列,我们不需要额外的连接:

SELECT
    c.Name AS City,
    co.Name AS Country,
    co.Continent AS Continent
FROM
    city AS c
INNER JOIN
    country AS co ON c.CountryCode = co.Code
ORDER BY
    co.Continent, co.Name, c.Name;

现在,让我们创建一个更复杂的场景。假设我们有一个名为 countrylanguage 的表,存储了每个国家使用的官方语言的信息。该表具有 CountryCodeLanguage 列。

要列出城市、它们的国家以及该国家的官方语言,我们需要将 citycountry 连接,然后将 countrycountrylanguage 连接。

SELECT
    c.Name AS City,
    co.Name AS Country,
    cl.Language AS OfficialLanguage
FROM
    city AS c
INNER JOIN
    country AS co ON c.CountryCode = co.Code
INNER JOIN
    countrylanguage AS cl ON co.Code = cl.CountryCode
WHERE
    cl.IsOfficial = 'T' -- 假设 'T' 代表官方语言
ORDER BY
    co.Name, c.Name, cl.Language;

在这里:

  • 第一个 INNER JOIN 使用 c.CountryCode = co.Codecity 连接到 country
  • 第二个 INNER JOIN 使用 co.Code = cl.CountryCodecountry 连接到 countrylanguage
  • 添加了一个 WHERE 子句来仅过滤官方语言,演示了 WHERE 子句如何与连接配合使用。

2.3 示例 3:结合 GROUP BY 进行统计

INNER JOIN 本质上会过滤掉没有匹配项的行。这意味着如果一个国家存在于 country 表中,但在 city 表中没有对应的条目(可能是新录入的国家或数据错误),那么该国家将不会出现在 INNER JOIN 的结果中。

这种行为可以用来计算或识别确实有匹配项的实体。例如,如果你想知道每个国家有多少个城市,你可以将 INNER JOINGROUP BYCOUNT 结合使用。

SELECT
    co.Name AS Country,
    COUNT(c.ID) AS NumberOfCities
FROM
    country AS co
INNER JOIN
    city AS c ON co.Code = c.CountryCode
GROUP BY
    co.Name
ORDER BY
    NumberOfCities DESC;

这个查询:

  1. 连接了 countrycity
  2. country.Name 对结果进行分组。
  3. 计算每个国家的城市 ID 数量。

INNER JOIN 确保结果集中仅包含在 city 表中至少拥有一个城市的国家。拥有零个城市的国家不会出现在这个结果集中。

3. 将 INNER JOIN 与 WHERE 和 ORDER BY 结合

通常,你需要对连接的结果进行过滤或排序。WHERE 子句和 ORDER BY 子句是在连接操作之后应用的。

以客户订单系统为例:检索由 'Alice Smith' 下的订单并按订单日期排序。

SELECT
    o.OrderID,
    o.OrderDate,
    o.TotalAmount,
    c.FirstName,
    c.LastName
FROM
    Orders AS o
INNER JOIN
    Customers AS c ON o.CustomerID = c.CustomerID
WHERE
    c.FirstName = 'Alice' AND c.LastName = 'Smith'
ORDER BY
    o.OrderDate DESC;

此查询首先连接 Orders(订单)和 Customers(客户)表。然后,它过滤连接后的结果,仅显示由 'Alice Smith' 下的订单。最后,它按 OrderDate(订单日期)降序排列这些特定的订单。