2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2

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)

观察结果集的两个方面

  • 海沃德市没有结果行。这是因为cities 表中没有海沃德的匹配条目,因此连接会忽略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 子句中的。

现在我们将弄清楚如何将海沃德记录找回来。我们希望查询做的是扫描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 列与所有其他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)

这里我们重新将天气表标记为w1w2,以便能够区分连接的左侧和右侧。您也可以在其他查询中使用这种别名来节省一些输入,例如

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

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



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

提交更正

如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用此表格 报告文档问题。