到目前为止,我们的查询每次只访问一个表。查询可以同时访问多个表,或者以这样一种方式访问同一个表,即同时处理该表的多个行。一次访问多个表(或同一个表的多个实例)的查询称为连接查询。它们将一个表中的行与第二个表中的行组合在一起,并使用一个表达式来指定要配对的行。例如,要返回所有天气记录以及相关城市的位置,数据库需要比较weather
表的每行的city
列与cities
表的所有行的name
列,并选择这些值匹配的行对。[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
表中不匹配的行。我们很快会看到如何解决这个问题。
有两列包含城市名称。这是正确的,因为weather
和cities
表的列列表被连接在一起。但在实践中,这是不可取的,因此您可能需要显式地列出输出列,而不是使用*
。
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;
此语法早于 SQL-92 中引入的JOIN
/ON
语法。这些表只是在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) 值。
练习: 还有右外连接和全外连接。试着找出它们的用途。
我们还可以将一个表与自身连接。这称为自连接。例如,假设我们希望查找在其他天气记录温度范围内的所有天气记录。因此,我们需要将每个weather
行的temp_lo
和temp_hi
列与所有其他weather
行的temp_lo
和temp_hi
列进行比较。我们可以使用以下查询来执行此操作
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;
您会经常遇到这种缩写风格。
如果您在文档中看到任何不正确的内容,与您使用特定功能的体验不符,或需要进一步澄清,请使用此表单来报告文档问题。