SQL中的连接(JOIN)语句用于将数据库中的两个或多个表组合起来。由连接生成的集合, 可以被保存为表, 或者当成表来使用. JOIN 语句的含义是把两张表的属性通过它们的值组合在一起. 基于 ANSI 标准的 SQL 列出了五种 JOIN 方式:内连接(INNER), 全外连接(FULL OUTER), 左外连接(LEFT OUTER), 右外连接(RIGHT OUTER)和交叉连接(CROSS). 在特定的情况下, 一张表(基本表, 视图, 或连接表)可以和自身进行连接, 成为自连接(self-join)。
内连接
内连接(inner join
程序要应该特别注意连接依据的列可能包含 NULL 值,NULL 值不与任何值匹配(甚至和它本身) -- 除非连接条件中显式地使用IS NULL或IS NOT NULL等谓词.
例如,下面的查询通过 Employee 表和 Department 表共有的属性 DepartmentID 连接两表。在两表 DepartmentID 匹配之处(如连接谓词被满足),查询将组合两表的LastName,DepartmentID和DepartmentName等列,把它们放到结果表的一行(一条记录)里。当 DepartmentID 不匹配,就不会往结果表中生成任何数据.
显式的内连接实例:
等价于:
显式的内连接的输出结果:
注:
相等链接
相等连接 (equi-join,或equijoin),是比较连接(θ连接)的一种特例,它的连接谓词只用了相等比较。使用其他比较操作符(如<)的不是相等连接。前面的查询已经展示了一个相等连接的实例:
SQL 提供了一种可选的简短符号去表达相等连接,它使用USING关键字 (Feature ID F402):
USING结构并不仅仅是
语法糖,上面查询的结果和使用显式谓词得到的查询得到的结果是不同的。特别地,在USING部分列出的列(column)将在连接结果的临时表中只出现一次,且无表名限定列名.在上面的例子中,连接结果的临时表产生单独的名为DepartmentID的列,而不是employee.DepartmentID或department.DepartmentID.
USING语句现已被 MySQL,Oracle,PostgreSQL,SQLite,和 DB2/400 等产品支持.
自然连接
自然连接比相等连接的进一步特例化。两表做自然连接时,两表中的所有名称相同的列都将被比较,这是隐式的。自然连接得到的结果表中,两表中名称相同的列只出现一次.
上面用于内连接的查询实例可以用自然连接的方式表示如下:
用了USING语句后,在连接表中,DepartmentID 列只出现一次,且没有表名作前缀:
交叉连接
交叉连接(cross join),又称
笛卡尔连接(cartesian join)或
叉乘(Product),它是所有类型的内连接的基础。把表视为行记录的集合,交叉连接即返回这两个集合的
笛卡尔积如果 A 和 B 是两个集合,它们的交叉连接就记为: A × B.
用于交叉连接的 SQL 代码在FROM列出表名,但并不包含任何过滤的连接谓词.
显式的交叉连接实例:
隐式的交叉连接实例:
交叉连接不会应用任何谓词去过滤结果表中的记录。程序员可以用WHERE语句进一步过滤结果集.
外连接
外连接并不要求连接的两表的每一条记录在对方表中都一条匹配的记录。要保留所有记录(甚至这条记录没有匹配的记录也要保留)的表称为保留表。 外连接可依据连接表保留左表, 右表或全部表的行而进一步分为左外连接, 右外连接和全连接.
(在这种情况下left<左> 和right<右> 表示JOIN关键字的两边.)
在标准的 SQL 语言中, 外连接没有隐式的连接符号.
外部连接既包含ON子句又包含WHERE子句时,应当只把表之间的连接条件写在ON子句中,对表中数据的筛选必须写在WHERE子句中。而内部连接的各条件表达式既可以放在ON子句又可以放在WHERE子句中。这是因为对于外部连接,保留表中被ON子句筛除掉的行要被添加回来,在此操作之后才会用WHERE子句去筛选连接结果中的各行。
左外连接
左外连接(left outer join), 亦简称为左连接(left join左外连接会返回左表的所有记录和右表中匹配记录的组合(如果右表中无匹配记录, 来自于右表的所有列的值设为 NULL). 如果左表的一行在右表中存在多个匹配行, 那么左表的行会复制和右表匹配行一样的数量, 并进行组合生成连接结果.
如, 这允许我们去找到雇员的部门时, 显示所有雇员, 即使这个雇员还没有关联的部门. (在上面的内连接部分由一个相反的例子, 没有关联的部门号的雇员在结果中是不显示的).
左外连接实例: (相对于内连接增添的行用斜体标出)
右外连接
右外连接, 亦简称右连接
右连接操作返回右表的所有行和这些行在左表中匹配的行(没有匹配的, 来源于左表的列值设为 NULL).
例如, 这允许我们在找每一个雇员以及他的部门信息时, 当这个部门里没有任何雇员时, 也把部门显示出来.
右连接的实例: (相对于内连接增添的行用斜体标出)
实际上显式的右连接很少使用, 因为它总是可以被替换成左连接--换换表的位置就可以了, 另外, 右连接相对于左连接并没有什么额外的功能. 上表同样可以使用左连接得到:
全连接
全连接是左右外连接的并集. 连接表包含被连接的表的所有记录, 如果缺少匹配的记录, 即以 NULL 填充.
如, 这允许我们查看每一个在部门里的员工和每一个拥有雇员的部门, 同时, 还能看到不在任何部门的员工以及没有任何员工的部门.
全连接实例:
一些数据库系统(如 MySQL)并不直接支持全连接, 但它们可以通过左右外连接的并集(参:union)来模拟实现. 和上面等价的实例:
SQLite 不支持右连接, 全外连接可以按照下面的方式模拟:
自连接
自连接就是和自身连接.下面的例子是一个很好的说明.
示例
构建一个查询, 它试图找到这样的记录: 每条记录包含两个雇员, 他们来自于同一个国家. 如果你有两张雇员表(Employee), 那么只要第一张表的雇员和第二张表的雇员在同样的国家的就行了, 你可以用一个通常的连接(相等连接)操作去得到这个表. 不过, 这里所有雇员信息都在一张单独的大表里.
下面一个修改过的雇员表Employee:
示例解决方案的查询可以写成如下:
它执行后将生成下面的表:
关于这个例子, 请注意:
只有当中的两行满足最初问题的要求, 第一项和最后一项对于本例来讲毫无用处。
替代方式
外连接查询得到的结果也可以通过
关联子查询得到. 例如
也可以写成如下样子:
连接算法
执行一个连接操作, 存在三种基本的算法.
类似于C语言编程时的双重循环。作为外层循环逐行扫描的表,称为外部输入表;针对外部输入表的每一行,要逐行扫描检查匹配的另一张表,称为内部输入表(相当于内层循环)。适用于外部输入表的行数较少,内部输入表创建了索引的情形。
合并连接(MERGE JOIN)
类似于两个有序数组的合并。两个输入表都在合并列上排序;然后依序对两张表逐行做连接或舍弃。如果预先建好了索引,合并连接的计算复杂度是线性的。
哈希连接(HASH JOIN)
适用于查询的中间结果,通常是无索引的临时表;以及中间结果的行数很大时。哈希连接选择行数较小的输入表作为生成输入,对其连接列值应用哈希函数,把其行(的存储位置)放入哈希桶中。