受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

2.6. 表之间的连接 #

到目前为止,我们的查询每次只访问一个表。查询也可以同时访问多个表,或者以某种 方式访问同一个表,使得该表的多行在同一时间被处理。一次访问多个表(或同一个表的 多个实例)的查询称为连接查询。它把一个表中的行与第二个 表中的行组合起来,并用一个表达式指定哪些行应当配对。例如,要返回所有天气记录及其 关联城市的位置,数据库需要将city列(来自 weather表的每一行)与name列 (来自cities表的所有行)进行比较,并选出这些值相匹配的行对。[4] 这可以通过下面的查询来实现:

SELECT * FROM weather JOIN cities ON city = name;
     city      | temp_lo | temp_hi | prcp |    date    |     name      | location
---------------+---------+---------+------+------------+---------------+-----------
 San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
 San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)
(2 rows)

注意结果集中的两点:

  • 没有与城市 Hayward 对应的结果行。这是因为cities表中 没有与 Hayward 匹配的项,所以连接会忽略weather表中 的不匹配行。我们很快就会看到如何修正这一点。

  • 有两列都包含城市名。这是正确的,因为weathercities表的列列表被串接在一起。不过在实际中这通常 并非所愿,因此你很可能会希望显式列出输出列,而不是使用*

    SELECT city, temp_lo, temp_hi, prcp, date, location
        FROM weather JOIN cities ON city = name;
    

由于这些列的名称都不同,解析器会自动判断它们属于哪个表。如果两个表中有重名列, 你就需要限定列名来说明你究竟想要哪一个,例如:

SELECT weather.city, weather.temp_lo, weather.temp_hi,
       weather.prcp, weather.date, cities.location
    FROM weather JOIN cities ON weather.city = cities.name;

在连接查询中限定所有列名通常被认为是一种好的风格,这样即使以后某个表中又添加了 同名列,查询也不会失败。

到目前为止看到的这种连接查询也可以写成下面这种形式:

SELECT *
    FROM weather, cities
    WHERE city = name;

这种语法早于JOIN/ON语法,后者是在 SQL-92 中引入的。各表只是简单地列在FROM子句中,而比较表达式 则被加到WHERE子句中。这种较旧的隐式语法与较新的显式 JOIN/ON语法得到的结果完全相同。但是对查询 的读者来说,显式语法更容易理解其含义:连接条件由自己的关键字引出,而以前它是与 WHERE子句中的其他条件混在一起的。

现在来看看怎样把 Hayward 的记录重新纳入结果中。我们希望这个查询扫描 weather表,并为其中的每一行找到匹配的 cities表行。如果找不到匹配行,我们就希望用一些 空值来替代cities表的列。这种查询 称为外连接。(我们到目前为止看到的连接都是 内连接。)命令如下:

SELECT *
    FROM weather LEFT OUTER JOIN cities ON weather.city = cities.name;
     city      | temp_lo | temp_hi | prcp |    date    |     name      | location
---------------+---------+---------+------+------------+---------------+-----------
 Hayward       |      37 |      54 |      | 1994-11-29 |               |
 San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
 San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)
(3 rows)

这个查询称为左外连接,因为位于连接操作符左侧的表中, 每一行在输出中至少都会出现一次,而右侧的表只有那些能与左侧表某一行匹配的行才会 出现在输出中。当某个左侧表行在右侧表中没有匹配项时,其对应输出行中的右侧表列将以 空值(null)填充。

练习:.  还有右外连接和全外连接。试着找出它们能做什么。

我们也可以把一个表与它自身连接。这称为自连接。 例如,假设我们想找出那些温度范围落在其他天气记录温度范围之内的天气记录。这样我们 就需要把temp_lotemp_hi列 (即每个weather表行中的这两列)与 temp_lotemp_hi列 (即所有其他weather表行中的这两列)进行比较。 我们可以用下面的查询实现这一点:

SELECT w1.city, w1.temp_lo AS low, w1.temp_hi AS high,
       w2.city, w2.temp_lo AS low, w2.temp_hi AS high
    FROM weather w1 JOIN weather w2
        ON w1.temp_lo < w2.temp_lo AND w1.temp_hi > w2.temp_hi;
     city      | low | high |     city      | low | high
---------------+-----+------+---------------+-----+------
 San Francisco |  43 |   57 | San Francisco |  46 |   50
 Hayward       |  37 |   54 | San Francisco |  46 |   50
(2 rows)

这里我们把 weather 表起别名为w1w2, 以便区分连接的左侧和右侧。你在其他查询中也可以用这类别名来少打一些字,例如:

SELECT *
    FROM weather w JOIN cities c ON w.city = c.name;

你会经常看到这种缩写风格。



[4] 这只是一个概念模型。连接通常会以比真正比较每一个可能的行对更高效的方式执行, 不过这对用户是不可见的。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。