PostgreSQL 18 中文参考文档(未翻译部分保留英文)🌐 切换到英文原版 →

2.6. 表之间的连接查询 #

到目前为止,我们的查询每次都只访问单张表。实际上,SQL 可以在单次查询中同时访问多张表,或者在同一张表内部同时比对多行数据。一次性访问多张表(或同一张表的多个镜像实例)的查询被称为连接(join)查询。连接操作通过指定连接表达式,将一张表中的数据行与另一张表中的数据行进行配对关联。例如,要将天气记录与对应城市的空间地理坐标一并返回,数据库需要将 weather 表每行的 city 字段与 cities 表每行的 name 字段进行比对,并筛选出两者匹配的行对。[4] 可以通过以下 SQL 语句实现连接:

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)

仔细观察上述返回结果集,可以发现两点:

由于两张表中字段名称各不相同,SQL 解析器能够自动识别各字段归属于哪张表。如果两张表中存在重名的列名,你必须对列名加上表前缀进行限定(qualify),以明确指定引用来源:

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

在编写连接查询时,显式为所有列带上表名或别名前缀是一种备受推崇的规范做法,这能防止未来若在某张表中新增了同名字段而导致既有 SQL 抛出字段歧义错误。

上述连接查询也可以采用如下传统语法书写:

SELECT *
    FROM weather, cities
    WHERE city = name;

这种语法早在 SQL-92 引入显式 JOIN/ON 语法之前就已经存在。它直接在 FROM 子句中平铺表名,并在 WHERE 子句中追加等值比对条件。这种较老的隐式连接与现代显式 JOIN/ON 的底层执行结果完全一致。但对于阅读代码的人来说,现代显式语法结构更加清晰:连接匹配条件由专用的关键字引入,与 WHERE 中普通的业务数据过滤条件清晰解耦。

现在我们来看如何让没有匹配记录的 Hayward 数据也出现在结果集中。我们希望查询扫描整张 weather 表,并尝试寻找对应的 cities 匹配行;若未找到匹配,则在结果中将 cities 表对应的字段填充为“空值”(Null)。这种查询被称为外连接(outer join,此前展示的连接均为内连接 inner join)。其语法如下:

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)

这种查询被称为左外连接(left outer join),因为位于连接操作符左侧的表(左表),其所有数据行都保证至少在结果中出现一次;而右侧的表(右表)则只输出与左表存在匹配的数据行。当输出某条没有右表匹配的左表记录时,右表对应的各个列将自动以 NULL 填充。

思考练习:.  SQL 中还存在右外连接(right outer join)与全外连接(full outer join)。不妨思考并测试它们分别代表什么行为。

我们还可以将一张表与自身进行连接,这被称为自连接(self join)。举个例子,假设我们希望找出所有气温范围完全覆盖了其他天气记录的数据行。我们需要将每条天气记录的最低气温和最高气温,与表中所有其他行的气温范围进行比对:

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)

此处我们分别为天气表指定了别名 w1 与 w2,从而能够清晰区分连接的左右两侧。表别名也可以广泛应用于其他日常查询中,以精简代码书写:

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

在实际工程中你将会频繁见到这种简写模式。



[4] 这只是一种便于理解的概念模型。数据库底层的优化器在物理执行连接时,通常采用哈希连接或合并连接等远比逐行交叉比对高效得多的算法,这一过程对用户完全透明。