半连接是当在子查询数据集存在一个匹配的行,从所述第一组返回行两个数据集之间的联接。
数据库在第一个匹配项时停止处理第二个数据集。因此,当第二数据集中的多个行满足子查询条件时,优化不会复制第一数据集中的行。
注意:半联接和反联接被视为联接类型,即使导致它们的SQL构造是子查询也是如此。它们是优化器用来展平子查询构造的内部算法,以便可以通过类似联接的方式对其进行解析。
半连接 和普通的内连接不同, 半连接实际上会去重。
9.3.3.1当优化器考虑半连接时
当查询仅需要确定是否存在匹配项时,半联接可避免返回大量行。
对于大型数据集,此优化可以节省大量的时间,而这些嵌套的循环连接必须遍历内部查询返回的每条记录(对于外部查询的每一行),而嵌套循环联接则必须如此。优化器可以将半联接优化应用于嵌套循环联接,哈希联接和排序合并联接。
在以下情况下,优化器可以选择半联接:
- 该语句使用
INorEXISTS子句。 - 该语句在
INorEXISTS子句中包含一个子查询。 - 该
IN或EXISTS条款不包含内部OR分支。
9.3.3.2半联接如何工作
半联接优化的实现方式取决于所使用的联接类型。
以下伪代码显示了嵌套循环连接的半连接:
FOR ds1_row IN ds1 LOOP
match := false;
FOR ds2_row IN ds2_subquery LOOP
IF (ds1_row matches ds2_row) THEN
match := true;
EXIT -- stop processing second data set when a match is found
END IF
END LOOP
IF (match = true) THEN
RETURN ds1_row
END IF
END LOOP
在前面的伪代码中,ds1是第一个数据集,并且ds2_subquery是子查询数据集。该代码从第一个数据集获取第一行,然后循环遍历子查询数据集以查找匹配项。一旦找到匹配项,该代码将退出内部循环,然后开始处理第一个数据集中的下一行。
示例9-13使用WHERE EXISTS的半联接
以下查询使用WHERE EXISTS子句仅列出包含员工的部门:
SELECT department_id, department_name
FROM departments
WHERE EXISTS (SELECT 1
FROM employees
WHERE employees.department_id = departments.department_id)
执行计划揭示了NESTED LOOPS SEMI步骤1中的操作:
--------------------------------------------------------------------------------
| Id| Operation | Name |Rows|Bytes|Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | NESTED LOOPS SEMI | | 11 | 209 | 2 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 2 (0)| 00:00:01 |
|*3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 44 | 132 | 0 (0)| |
--------------------------------------------------------------------------------
对于departments组成外部循环的中的每一行,数据库获取部门ID,然后探测employees.department_id索引以查找匹配的条目。从概念上讲,索引如下所示:
10,rowid
10,rowid
10,rowid
10,rowid
30,rowid
30,rowid
30,rowid
...
如果表中的第一个条目departments是department 30,那么数据库将对索引执行范围扫描,直到找到第一个30条目为止,这时它将停止读取索引并从中返回匹配的行departments。如果外部循环的下一行是department 20,则数据库将扫描索引以20查找条目,并且未找到任何匹配项,则执行外部循环的下一次迭代。数据库以这种方式进行,直到返回所有匹配的行。
示例9-14使用IN的半联接
以下查询使用IN子句仅列出包含员工的部门:
SELECT department_id, department_name
FROM departments
WHERE department_id IN
(SELECT department_id
FROM employees);
执行计划揭示了NESTED LOOPS SEMI步骤1中的操作:
--------------------------------------------------------------------------------
| Id| Operation | Name |Rows|Bytes|Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | NESTED LOOPS SEMI | | 11 | 209 | 2 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 2 (0)| 00:00:01 |
|*3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 44 | 132 | 0 (0)| |
--------------------------------------------------------------------------------
该计划与示例9-13中的计划相同。




