顾名思义,本节重点是使用SQL将两个或多个数据库表连接到一个表中,这是数据科学家的基本技能。内部联接(inner join),和左联接可能是两种最常见的联接。
本文内容整理自 Datacamp| Joining Data in PostgreSQL

Inner Join介绍

初始数据图(Initial data diagram)

我们可以看到id字段的匹配值用相同的颜色着色。id字段被称为键字段(key field),因为它可以用来将一个表引用到另一个表。left_table和right_table都有另一个名为val的字段。

内部连接图

内部联接只包括键在两个表中的记录。这里可以看到id字段只匹配值1和4。使用内部联接,我们在right_table中查找与left_table中键字段中的所有条目对应的匹配项。

所以这里只关注那些id字段匹配的记录, 而不属于内部连接的记录已经消失了。下面是由INNER JOIN得到的表,它给出了right_table的val字段,其中的记录只对应于id值为1或4的记录,分别被涂成了黄色和紫色。

用以实现这一过程的代码为:

SELECT left_table.id AS L_id
 		left_table.val AS L_val
 		right_table.val AS R_val
FROM left_table
INNER JOIN right_table
ON left_table.id = right_table.id

例子

这里,我们给出两个表, Prime Minister 表 和 President 表,如下:

Prime Minister

countrycontinentprime_minister
EgyptAfricaSherif Ismail
PortugalEuropeAntonio Costa
VietnamAisaNguyen Xuan Phuc
HaitiNorth AmericaJack Guy Lafontant
IndiaAsiaNarendra Modi
AustraliaOceaniaMalcolm Turnbull
NorwayEuropeErna Solberg
BruneiAsiaHassanal Bolkiah
OmanAsiaQaboos bin Said al Said
SpainEuropeMariano Rajoy

President

countrycontinentpresident
EgyptAfricaAbdel Fattah el-Sisi
PortugalEuropeMarcelo Rebelo de Sousa
HaitiNorth AmericaJovenel Moise
UruguaySouth AmericaJose Mujica
LiberiaAfricaEllen Johnson Sirleaf
ChileSouth AmericaMichelle Bachelet
VietnamAisaTran Dai Quang

用以下代码, 我们能够实现两个表依据country的inner join。

SELECT p1.country, p1.continent, prime_minister, president
FROM prime_ministers AS p1 
INNER JOIN presidents AS p2 
ON p1.country = p2.country;

请注意,首相和总统的别名分别为p1和p2,对于较长的表名,这样有助于简化代码。SELECT语句用于从两个表中选择特定字段。在这种情况下,由于country在这两个表中都存在,因此必须写入p1.以避免SQL错误。接下来,我们在FROM之后列出内部联接左侧的表,然后在INNER JOIN之后列出右侧的表。最后,我们在两个表中指定要匹配的键。

连接两张表

在单个查询中组合多个联接的能力是SQL的一个强大功能,例如

SELECT *
FROM left_table
  INNER JOIN right_table
    ON left_table.id = right_table.id
  INNER JOIN another_table
    ON left_table.id = another_table.id;

INNER JOIN via USING

如果要联接的键字段在两个表中的名称相同,则可以使用USING子句,而不是迄今为止看到的on子句。因为id在left_table和right_table中都是相同的名称,所以我们可以在这里使用USING而不是ON。需要注意的是,关键字字段周围需要使用括号。

那么在上述president和prime_minister的例子中,代码也可写为:

SELECT p1.country, p1.continent, prime_minister, president FROM presidents AS p1
INNER JOIN prime_ministers AS p2
USING (country);

得到结果为:

countrycontinentprime_ministerpresident
EgyptAfricaSherif IsmailAbdel Fattah el-Sisi
PortugalEuropeAntonio CostaMarcelo Rebelo de Sousa
VietnamAsiaNguyen Xuan PhucTran Dai Quang
HaitiNorth AmericaJack Guy LafontantJovenel Moise

由于inner join在两个表中都包含条目,并且两个表都包含列出的country,因此如果选择这些列,则将表放置在联接中的顺序无关紧要。但是通常我们需要明确在左边和右边分别使用哪张表,以避免混淆。

Self Join 自连接

自联接用于将字段中的值与同一表中同一字段的其他值进行比较。
我们可以再次用prime_minister表对此进行探究。一个可能的问题是:如何两两匹配属于同一大洲的国家?

SELECT p1.country AS country1, p2.country AS country2, p1.continent 
FROM prime_ministers AS p1
INNER JOIN prime_ministers AS p2
ON p1.continent = p2.continent
LIMIT 14;
country1country2continent
EgyptEgyptAfrica
PortugalSpainEurope
PortugalNorwayEurope
PortugalPortugalEurope
VietnamOmanAsia
VietnamBruneiAsia
VietnamIndiaAsia
VietnamVietnamAsia
HaitiHaitiNorth America
IndiaOmanAsia
IndiaBruneiAsia
IndiaIndiaAsia
IndiaVietnamAsia
AustraliaAustraliaOceania

我们得到以上结果,它确实是每个国家与同一个大陆上其他国家的配对。但是,我们通常不想把国家和它自己列在一起。因此我们可以利用and,将代码可以调整为:

SELECT p1.country AS country1, p2.country AS country2, p1.continent 
FROM prime_ministers AS p1
INNER JOIN prime_ministers AS p2
ON p1.continent = p2.continent AND p1.country <> p2.country
LIMIT 13;

CASE WHEN, 和 THEN

CASE是一种在SQL中,以简化的方式执行多个if-then-else语句的方法。
我们引入一个新的表 states来举例, states表包含世界六大洲不同国家的numeric数据,其内容如下:

namecontinentindep_year
AustraliaOceania1901
BruneiAsia1984
ChileSouth America1810
EgyptAfrica1922
HaitiNorth America1804
IndiaAsia1947
LiberiaAfrica1847
NorwayEurope1905
OmanAsia1951
PortugalEurope1143
SpainEurope1492
UruguaySouth America1828
VietnamAsia1945

假设我们想把独立年分成1900年之前,1900年到1930年之间,以及1930年之后:

SELECT name, continent, indep_year,
CASE WHEN indep_year < 1900 THEN 'before 1900'
	 WHEN indep_year <= 1930 THEN 'between 1900 and 1930' 
	 ELSE 'after 1930' END
	 AS indep_year_group
FROM states
ORDER BY indep_year_group;

在第一个WHEN之后,我们要检查indep_year是否小于1900。接下来,我们希望indep_year_group 获取“between 1900 and 1930”的那些记录。最后,不符合这些条件的任何其他记录将被指定为indep_year_group的值 ’ after 1930’。得到结果如下:

namecontinentindep_yearindep_year_group
BruneiAsia1984after 1930
IndiaAsia1947after 1930
OmanAsia1951after 1930
VietnamAsia1945after 1930
LiberiaAfrica1847before 1900
ChileSouth America1810before 1900
HaitiNorth America1804before 1900
PortugalEurope1143before 1900
SpainEurope1492before 1900
UruguaySouth America1828before 1900
NorwayEurope1905between 1900 and 1930
AustraliaOceania1901between 1900 and 1930
EgyptAfrica1922between 1900 and 1930

此外, 我们还可以使用INTO 保存上一个查询的结果到一张新的表。
例如,有表populations, 前6行如下:

pop_idcountry_codeyearfertility_ratelife_expectancysize
20ABW20101.70474.9535101597
19ABW20151.64775.5736103889
2AFG20105.74658.970827962200
1AFG20154.65360.717232526600
12AGO20106.41650.654221220000
11AGO20155.99652.666125022000

根据他们的人口2015年的size, 我们将其分为‘large’,‘small’, ‘medium’三个组,

SELECT country_code, size,
    CASE WHEN size > 50000000 THEN 'large'
        WHEN size > 1000000 THEN 'medium'
        ELSE 'small' END
        AS popsize_group
-- 1. Into table
into pop_plus
FROM populations
WHERE year = 2015;

-- 2. Select all columns of pop_plus
Select *
from pop_plus

结果如下:

country_codesizepopsize_group
ABW103889small
AFG32526600medium
AGO25022000medium
ALB2889170medium
AND70473small

更多推荐

SQL新手入门 详细总结笔记(三)| Joining Data in PostgreSQL