简介
将相关数据拆分为单独的表,从一致性、灵活性和某些类型的性能的角度来看可能是有益的。但是,当相关信息跨越多个表时,你仍然需要一个合理的方法来重新整合记录。
在关系型数据库中,连接 提供了一种根据公共字段值合并两个或多个表中的记录的方法。不同类型的连接可以根据如何处理不匹配的行来实现不同的结果。在本指南中,我们将讨论 PostgreSQL 提供的各种连接类型,以及如何使用它们从多个来源合并表数据。
什么是连接?
简而言之,连接 是一种显示来自多个表的数据的方法。它们通过根据某些列中的匹配值将来自不同来源的记录缝合在一起。每个生成的 row 由第一个表中的记录和第二个表中的记录组成,基于每个表中一个或多个列具有相同的值。
连接的基本语法如下所示
SELECT*FROM<first_table><join_type> <second_table><join_condition>;
在连接中,每个生成的 row 由第一个表的所有列加上第二个表的所有列构成。查询的 SELECT
部分可用于指定你想要显示的精确列。
如果用于比较的列中的值不唯一,则可能从原始表构建多个行。例如,假设你有一个来自第一个表的列,该列在比较列中具有两个值为“red”的记录。与之匹配的是来自第二个表的列,该列具有三个具有该值的 row。连接将生成六个不同的 row 来表示可以实现的各种组合。
连接类型和连接条件决定了如何构造每个显示的 row。这会影响对每个表中在连接条件上具有和不具有匹配项的行执行的操作。
为了方便起见,许多连接将一个表上的主键与第二个表上的关联外键匹配。虽然主键和外键仅用于数据库系统以维护一致性保证,但它们的关系通常使它们成为连接条件的良好候选者。
不同类型的连接
提供了各种类型的连接,每种连接都可能产生不同的结果。了解每种类型是如何构建的将帮助你确定哪种类型适合不同的场景。
内连接
默认连接称为内连接。在 PostgreSQL 中,这可以使用 INNER JOIN
或只是简单的 JOIN
来指定。
这是一个演示内连接语法的典型示例
SELECT*FROMtable_1[INNER] JOIN table_2ON table_1.id = table_2.table_1_id;
内连接是最严格的连接类型,因为它只显示通过组合两个表中的行创建的行。任何在组成表中没有在另一个表中具有匹配对应项的行都会从结果中删除。例如,如果第一个表在比较列中具有值为“blue”的值,而第二个表没有记录具有该值,则该行将从输出中屏蔽。
如果将结果表示为组成表的 Venn 图,则内连接允许你表示两个圆圈的重叠区域。只有在一个表中存在的任何值都不会显示。
左连接
左连接 是一种显示内连接中找到的所有记录,加上第一个表中的所有不匹配行的连接。在 PostgreSQL 中,这可以指定为 LEFT OUTER JOIN
或只是 LEFT JOIN
。
左连接的基本语法遵循以下模式
SELECT*FROMtable_1LEFT JOIN table_2ON table_1.id = table_2.table_1_id;
左连接通过首先执行内连接来构造两个表中的所有匹配记录的行。之后,第一个表中的不匹配记录也将被包含在内。由于每个连接中的 row 都包含两个表的列,因此不匹配的列使用 NULL
作为第二个表中所有列的值。
如果将结果表示为组成表的 Venn 图,则左连接允许你表示整个左侧圆圈。由两个圆圈之间的交集表示的左侧圆圈的部分将具有由右侧表补充的额外数据。
右连接
一个 右连接 是一个连接,它显示在内部连接中找到的所有记录,以及来自第二个表的所有不匹配行。在 PostgreSQL 中,这可以指定为 RIGHT OUTER JOIN
或者只是 RIGHT JOIN
。
右连接的基本语法遵循以下模式
SELECT*FROMtable_1RIGHT JOIN table_2ON table_1.id = table_2.table_1_id;
右连接通过首先执行内部连接来构建来自两个表中所有匹配记录的行来构建。之后,还包括来自第二个表的未匹配记录。由于连接中的每一行都包含两个表的列,因此未匹配的列使用 NULL
作为第一个表中所有列的值。
如果你将结果表示为组成表的维恩图,那么右连接可以让你表示整个右圆圈。右圆圈中由两个圆圈之间的交集表示的部分将从左表中补充额外的。数据。
全连接
一个 全连接 是一个连接,它显示在内部连接中找到的所有记录,以及来自两个组成表的所有不匹配行。在 PostgreSQL 中,这可以指定为 FULL OUTER JOIN
或者只是 FULL JOIN
。
全连接的基本语法遵循以下模式
SELECT*FROMtable_1FULL JOIN table_2ON table_1.id = table_2.table_1_id;
全连接通过首先执行内部连接来构建来自两个表中所有匹配记录的行来构建。之后,还包括来自两个表的未匹配记录。由于连接中的每一行都包含两个表的列,因此未匹配的列使用 NULL
作为未匹配其他表中所有列的值。
如果你将结果表示为组成表的维恩图,那么全连接可以让你完全表示两个组成圆圈。两个圆圈的交集将具有由每个组成表提供的。值。圆圈中不在重叠区域的部分将具有它们所属表的。值,使用 NULL
来填充在另一个表中找到的列。
交叉连接
还提供了一种特殊的连接,称为 CROSS JOIN
。交叉连接不使用任何比较来确定每个表中的行是否匹配。相反,结果是通过简单地将第一个表中的每一行添加到第二个表中的每一行来构建的。
这会产生两个或多个表的行的笛卡尔积。实际上,这种连接方式无条件地组合了来自每个表的行。因此,如果每个表都有三行,则结果表将有九行,其中包含来自两个表的。所有列。
例如,如果你有一个名为 t1
的表与一个名为 t2
的表结合,每个表都有行 r1
、r2
和 r3
,则结果将是九行,如下所示。
t1.r1 + t2.r1t1.r1 + t2.r2t1.r1 + t2.r3t1.r2 + t2.r1t1.r2 + t2.r2t1.r2 + t2.r3t1.r3 + t2.r1t1.r3 + t2.r2t1.r3 + t2.r3
自连接
自连接是任何将表的行与自身结合的连接。可能不清楚这如何有用,但它实际上有许多常见的应用。
通常,表描述可以在彼此的关系中扮演多种角色的实体。例如,如果你有一个 people
表,则每一行都可能包含一个 mother
列,该列引用表中的其他 people
。自连接将允许你通过将第二个表的实例连接到第一个表的实例来将这些不同的行缝合在一起,其中这些值匹配。
由于自连接两次引用同一个表,因此需要表别名来消除歧义引用。例如,在上面的示例中,你可以使用别名 people AS children
和 people AS mothers
连接 people
表的两个实例。这样,你就可以在定义连接条件时指定要引用的表的哪个实例。
这是另一个示例,这次表示员工和经理之间的关系
SELECT*FROMpeople AS employeeJOIN people AS managerON employee.manager_id = manager.id;
连接条件
当组合表时,连接条件决定了如何将行匹配在一起以形成组合结果。基本前提是定义每个表中必须匹配的列,以便连接在该行上发生。
ON
子句
定义表连接条件的最标准方法是使用 ON
子句。 ON
子句使用等号来指定将比较的每个表中的精确列,以确定何时可能发生连接。PostgreSQL 使用提供的列将来自每个表的行缝合在一起。
ON
子句是最详细的,但也是所有可用连接条件中最灵活的。它允许在组合的每个表的列名如何标准化方面提供特异性。
ON
子句的基本语法如下所示
SELECT*FROMtable1JOINtable2ONtable1.id = table2.ident;
在这里,来自 table1
和 table2
的行将在 table1
中的 id
列与 table2
中的 ident
列匹配时连接起来。由于使用了内部连接,因此结果将只显示已连接的行。由于查询使用了通配符 *
字符,因此将显示来自两个表的. 所有列。
这意味着将显示 table1
中的 id
列和 table2
中的 ident
列,即使它们具有完全相同的值,因为它们满足了连接条件。你可以通过在 SELECT
列列表中调用要显示的精确列来避免这种重复。
USING
子句
USING
子句是指定 ON
子句条件的简写,当要比较的列在两个表中具有相同名称时可以使用该子句。 USING
子句采用一个列表(括在括号中),其中包含应该比较的共享列名。
USING
子句的一般语法使用以下格式
SELECT*FROMtable1JOINtable2USING(id, state);
此连接在两个表都共享的两个列(id
和 state
)都具有匹配值时,将 table1
与 table2
结合起来。
此相同的连接可以使用 ON
更详细地表达,如下所示
SELECT*FROMtable1JOINtable2ONtable1.id = table2.id AND table1.state = table2.state;
虽然以上两个连接都会产生相同的行,并具有相同的数据,但它们会以略微不同的方式显示。虽然 ON
子句包含来自两个表的所有列,但 USING
子句会抑制重复列。因此,而不是有两个单独的 id
列和两个单独的 state
列(一个用于每个表),结果将只有一个 id
列和一个 state
列,后面跟着由 table1
和 table2
提供的所有其他列。
NATURAL
子句
NATURAL
子句是另一个简写,可以进一步减少 USING
子句的冗长。 NATURAL
连接不会指定任何要匹配的列。相反,PostgreSQL 将自动基于每个数据库中具有匹配列的所有列来连接表。
NATURAL
连接子句的一般语法如下所示
SELECT*FROMtable1NATURAL JOINtable2;
假设 table1
和 table2
都有名为 id
、state
和 company
的列,则上面的查询将等效于使用 ON
子句的以下查询
SELECT*FROMtable1JOINtable2ONtable1.id = table2.id AND table1.state = table2.state AND table1.company = table2.company;
以及使用 USING
子句的以下查询
SELECT*FROMtable1JOINtable2USING(id, state, company);
与 USING
子句类似, NATURAL
子句会抑制重复列,因此结果中只会存在每个连接列的一个实例。
虽然 NATURAL
子句可以减少查询的冗长,但在使用时必须谨慎。由于用于连接表的列是自动计算的,如果组件表中的列发生变化,结果可能会有很大差异,因为新的连接条件会导致不同的结果。
连接条件和 WHERE
子句
连接条件与使用 WHERE
子句筛选数据行的比较操作具有很多共同特征。这两种结构都定义了必须评估为真的表达式,才能考虑该行。因此,在 WHERE
结构中包含额外的比较操作与在连接子句本身中定义它们之间区别并不总是直观的。
为了理解由此产生的差异,我们必须查看 PostgreSQL 处理查询不同部分的顺序。在本例中,连接条件中的谓词将首先被处理,以在内存中构造虚拟连接表。在此阶段之后,将评估 WHERE
子句中的表达式以筛选结果行。
例如,假设我们有两个表,名为 customer
和 order
,我们需要将它们连接在一起。我们希望通过将 customer.id
列与 order.customer_id
列匹配来连接这两个表。此外,我们对 order
表中 product_id
为 12345 的行感兴趣。
鉴于上述要求,我们关心两个条件。但是,表达这些条件的方式将决定我们得到的结果。
首先,让我们将两者都用作 LEFT JOIN
的连接条件
SELECTcustomer.id AS customer_id,customer.name,order.id AS order_id,order.product_idFROMcustomerLEFT JOINorderONcustomer.id = order.customer_id AND order.product_id = 12345;
结果可能看起来像这样
customer_id | name | order_id | product_id------------+----------+----------+------------4380 | Acme Co | 480 | 123454380 | Acme Co | 182 | 12345320 | Other Co | 680 | 123454380 | Acme Co | |320 | Other Co | |20 | Early Co | |8033 | Big Co | |(7 rows)
PostgreSQL 通过执行以下操作得出了此结果
- 将
customer
表中的任何行与order
表中的行合并,其中customer.id
匹配order.customer_id
。order.product_id
匹配 12345
- 由于我们使用的是左连接,因此包括左侧表 (
customer
) 中的任何不匹配行,并用NULL
值填充右侧表 (order
) 中的列。 - 仅显示
SELECT
列规范中列出的列。
结果是,我们所有连接的行都匹配我们正在寻找的两个条件。但是,左连接导致 PostgreSQL 还包括第一个表中任何不满足连接条件的行。这会导致“剩余”行,这些行似乎不符合查询的明显意图。
如果我们将第二个查询 (order.product_id
= 12345) 移动到 WHERE
子句中,而不是将其作为连接条件包含在内,我们将得到不同的结果
SELECTcustomer.id AS customer_id,customer.name,order.id AS order_id,order.product_idFROMcustomerLEFT JOINorderONcustomer.id = order.customer_idWHEREorder.product_id = 12345;
这一次,仅显示三行
customer_id | name | order_id | product_id------------+----------+----------+------------4380 | Acme Co | 480 | 123454380 | Acme Co | 182 | 12345320 | Other Co | 680 | 12345(3 rows)
比较执行的顺序是这些差异的原因。这一次,PostgreSQL 像这样处理查询
- 将
customer
表中的任何行与order
表中的行合并,其中customer.id
匹配order.customer_id
。 - 由于我们使用的是左连接,因此包括左侧表 (
customer
) 中的任何不匹配行,并用NULL
值填充右侧表 (order
) 中的列。 - 评估
WHERE
子句以删除order.product_id
列的值不为 12345 的任何行。 - 仅显示
SELECT
列规范中列出的列。
这一次,即使我们使用的是左连接,WHERE
子句也会通过筛选所有没有正确 product_id
的行来截断结果。由于任何不匹配的行都将 product_id
设置为 NULL
,因此这将删除左连接填充的所有不匹配的行。它还会删除任何通过连接条件匹配但在第二轮检查中未通过的匹配行。
了解 PostgreSQL 用于执行查询的基本过程可以帮助您在处理数据时避免一些容易犯但难以调试的错误。
结论
在本指南中,我们介绍了连接如何使关系数据库能够将来自不同表的数据组合在一起,以提供更有价值的答案。我们讨论了 PostgreSQL 支持的各种连接、每种类型组装结果的方式以及使用特定类型的连接时应该期待的内容。之后,我们介绍了定义连接条件的不同方法,并查看了连接和 WHERE
子句之间的相互作用如何导致意外结果。
连接是使关系数据库功能强大且灵活足以处理如此多种不同类型的查询的关键部分。使用逻辑边界组织数据,同时仍然能够在逐案基础上以新颖的方式重新组合数据,赋予像 PostgreSQL 这样的关系数据库不可思议的多功能性。学习如何在表之间进行这种缝合将使您能够创建更复杂的查询,并依赖数据库来创建数据的完整视图。
常见问题解答
是的,PostgreSQL 支持外连接。例如,您可以使用 LEFT OUTER JOIN
或像下面这样使用 LEFT JOIN
SELECT*FROMtable_1LEFT JOIN table_2ON table_1.id = table_2.table_1_id;
PostgreSQL 中的 LATERAL
关键字可以位于子选择 FROM 项目之前,并允许子选择引用 FROM 列表中出现在其之前的项目的列。(没有 LATERAL,每个子选择都是独立评估的,因此无法交叉引用任何其他 FROM 项目。)
是的,可以在 PostgreSQL 中执行 CROSS JOIN
。语法看起来像这样
SELECT select_listFROM t1CROSS JOIN t2;
上面的示例将显示类似于此 示例输出的内容
是的,PostgreSQL 支持全连接。它们可以指定为 FULL OUTER JOIN
或 FULL JOIN
。
语法看起来像这样
SELECT*FROMtable_1FULL JOIN table_2ON table_1.id = table_2.table_1_id;
PostgreSQL 中的默认连接是内连接,可以通过使用 INNER JOIN
或 JOIN
来指定。
语法是
SELECT*FROMtable_1[INNER] JOIN table_2ON table_1.id = table_2.table_1_id;