半聯結:是在兩個數據集之間的聯結,其中第一個數據庥中的數據行在決定是否返回時會根據在別一個數據集中出現或不出現至少一個相匹配的數據行來確定。
標準的內聯結與半聯結之間最主要的區別在于在半聯結中,第1個數據集中的每一條記錄只返回一次,而不管在第2個數據集中有幾條匹配數據。這個定義表明這個查詢的實際處理過程可以通過在找到第一個匹配以后馬上停止處理該查詢來進行優化。這種聯結技術在oracle基于成本的優化器中當查詢中又包含在in或exists子句中(或者包含在很少使用的與in同義的=any子句中)的子查詢時是一種可選方案。
--in半聯結
SELECT /* using in */ dept.department_name FROM hr.departments dept
WHERE dept.department_id IN (SELECT emp.department_id FROM hr.employees emp)
--exists半聯結
SELECT /* using exists */ dept.department_name FROM hr.departments dept
WHERE EXISTS (SELECT NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
這兩個查詢在功能上是等價的,也就是說如果輸入相同,它們總是返回同樣的數據集。
--exists和in的可替換語法--inner join
SELECT /* inner join */ dept.department_name
FROM hr.departments dept, hr.employees emp
WHERE dept.department_id=emp.department_id;
顯示內聯結在功能上與半聯結并不是等價的,因為返回的行數不同。這里有很多重復的值,可以用distinct去重。
--exists和in的可替換語法--具有distinct的inner join
SELECT /* inner join with distinct */ DISTINCT dept.department_name
FROM hr.departments dept, hr.employees emp
WHERE dept.department_id=emp.department_id;
--exists和in的可替換語法--丑陋的交集
SELECT /* ugly intersect */ dept.department_name
FROM hr.departments dept,
(
SELECT department_id FROM hr.departments
INTERSECT
SELECT department_id FROM hr.employees
) b
WHERE b.department_id=dept.department_id;
--exists和in的可替換語法--any 子查詢
SELECT /* any subquery */ dept.department_name
FROM hr.departments dept
WHERE dept.department_id = ANY (SELECT department_id FROM hr.employees emp);
關于any的版本,它就是in的一個可替換寫法。
--半聯結與distinct是不同的
SELECT /* SEMI using IN */ e.department_id
FROM hr.employees e
WHERE e.department_id IN (SELECT department_id FROM hr.departments);
SELECT /* inner join with distinct */ DISTINCT e.department_id
FROM hr.departments dept,hr.employees e
WHERE e.department_id=dept.department_id;
半聯結與帶distinct的內聯結是不等價的。in/exists方式取出第一個集合中的每條記錄,如果在第2個集合中至少有一條記錄與之相匹配,則返回這個記錄。因此,假設查詢1返回的結果中有重復的值,結果集中就有可能會有重復值。distinct方式取出所有數據行,進行排序,然后將重復行舍棄掉。由此可以知道,distinct版本最終可能會多做很多工作,因為它沒有機會提前從子查詢中跳出。
使用exists語法還有另一個有必要提及的常見錯誤。如果使用exists,要確定子查詢與外層查詢是相關的。如果子查詢沒有涉及外層查詢則是無意義的。
--使用exists常見錯誤--不相關查詢
SELECT /* corrected */ e.department_id
FROM hr.employees e
WHERE EXISTS (SELECT dept.department_id FROM hr.departments dept
WHERE e.department_id=dept.department_id
);
SELECT /* not corrected */ e.department_id
FROM hr.employees e
WHERE EXISTS (SELECT dept.department_id FROM hr.departments dept);
SELECT /* non-correclated totally unrelated */ dept.department_id
FROM hr.departments dept
WHERE EXISTS(SELECT NULL FROM dual);
SELECT /* non-correclated empty subquery */ dept.department_id
FROM hr.departments dept
WHERE EXISTS(SELECT 'anything' FROM dual WHERE 1=2);
可見相關查詢得到了我們想要的記錄(也就是說,只有在第2個查詢中有匹配行的數據)。顯然,不相關子查詢沒有得到想要的結果。它們返回了第1個表中所有的記錄,如果你直接寫針對第1個表的查詢也會得到這樣的結果。從倒數第2個例子,具有對dual非相關的子查詢,可以看出,不管子查詢是什么,第一個表中的所有記錄都會返回。最后一個例子說明當子查詢沒有任何記錄返回時會發生什么,這種情況不會返回任何記錄。
半聯結的執行計劃
Oracle中最常用的3種聯結方法是嵌套循環,散列聯結和合并聯結。
SQL> set autotrace traceonly
SQL> --半聯結的執行計劃
SQL> SELECT dept.department_name
2 FROM hr.departments dept
3 WHERE dept.department_id IN (SELECT e.department_id FROM hr.employees e);
已選擇11行。
執行計劃
----------------------------------------------------------
Plan hash value: 2605691773
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 190 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS SEMI | | 10 | 190 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 41 | 123 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("DEPT"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
統計信息
----------------------------------------------------------
1 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
743 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
11 rows processed
SQL> SELECT /* exists */ dept.department_name
2 FROM hr.departments dept
3 WHERE EXISTS(SELECT NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
已選擇11行。
執行計劃
----------------------------------------------------------
Plan hash value: 2605691773
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 190 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS SEMI | | 10 | 190 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 41 | 123 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("EMP"."DEPARTMENT_ID"="DEPT"."DEPARTMENT_ID")
統計信息
----------------------------------------------------------
1 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
743 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
11 rows processed
上面兩個語句的確是用同樣的方法來處理的執行計劃是一致的,統計信息也是一致的。優化器能夠并且將這兩種形式的查詢轉換成同樣的語句。
使用提示控制半聯結執行計劃
* semijon進行半聯結,優化器選擇使用哪種類型
* no_semijon不進行半聯結
* nl_sj進行嵌套半聯結,10起被棄用
* hash_sj進行散列半聯結,10起被棄用
* merge_sg進行合并半聯結,10起被棄用
--使用no_semijon提示的exists語句
SELECT /* exists no_semijoin */ dept.department_name
FROM hr.departments dept
WHERE EXISTS(SELECT /*+ no_semijoin */ NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
set autotrace trace;
執行計劃
----------------------------------------------------------
Plan hash value: 440241596
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 16 | 17 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 2 | 6 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS (SELECT 0 FROM "HR"."EMPLOYEES" "EMP" WHERE
"EMP"."DEPARTMENT_ID"=:B1))
3 - access("EMP"."DEPARTMENT_ID"=:B1)
使用no_semijoin提示禁止優化器使用半聯結,如期望那樣,查詢不再進行半聯結,而是使用filter運算將兩個行數據源結合起來。
實驗級控制半聯結執行計劃
默認值為choose,允許優化器對所有半聯結方法進行評估并選擇它認為是最高效的方法。將參數設置為hash,merge或nested_loops就將優化器的選擇限定為指定的聯結方法。
--_always_semi_join的有效值
SELECT name_kspvld_values name,value_kspvld_values value FROM x$kspvld_values
WHERE name_kspvld_values LIKE NVL('&name',name_kspvld_values);
--輸入_always_semi_join
SQL> --使用_always_semi_join將執行計劃改變為nested_loops半聯結
SQL> ALTER SESSION SET "_always_semi_join"=merge;
會話已更改。
SQL> SELECT /* using in */ dept.department_name
2 FROM hr.departments dept
3 WHERE dept.department_id IN (SELECT department_id FROM hr.employees emp);
已選擇11行。
執行計劃
----------------------------------------------------------
Plan hash value: 954076352
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost(%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 190 | 4 (25)| 00:00:01 |
| 1 | MERGE JOIN SEMI | | 10 | 190 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 27 | 432 | 2 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | DEPT_ID_PK | 27 | | 1 (0)| 00:00:01 |
|* 4 | SORT UNIQUE | | 107 | 321 | 2 (50)| 00:00:01 |
| 5 | INDEX FULL SCAN | EMP_DEPARTMENT_IX | 107 | 321 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("DEPT"."DEPARTMENT_ID"="DEPARTMENT_ID")
filter("DEPT"."DEPARTMENT_ID"="DEPARTMENT_ID")
SQL> ALTER SESSION SET "_always_semi_join"=nested_loops;
SQL> SELECT /* using in */ dept.department_name
2 FROM hr.departments dept
3 WHERE dept.department_id IN (SELECT department_id FROM hr.employees emp);
已選擇11行。
執行計劃
----------------------------------------------------------
Plan hash value: 2605691773
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 190 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS SEMI | | 10 | 190 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 41 | 123 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("DEPT"."DEPARTMENT_ID"="DEPARTMENT_ID")
關聯結的限制條件
對于優化器選擇使用半聯結文檔中,只說明了一個主要限制條件,11gR2中。優化器不會為任何包含or分支中的子查詢選擇半聯結。在之前的oracle版本中,包含distinct關鍵字時也會禁用半聯結,但現在己經沒有這個限制了。
--在oracle 11gR2中禁用or分支中的半聯結
SELECT /* exists with or */ dept.department_name
FROM hr.departments dept
WHERE 1=2 OR EXISTS (SELECT NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
半聯結的必要條件
半聯結是一種可以極大提升某些查詢性能的優化方法。基于成器的優化器決定選用半聯結的必要條件:
- 語句必須使用關鍵字in(=ANY)或exists
- 語句必須在in或exists子句中有子查詢
- 如果語句使用exists語法,則必須使用相關子查詢
- in和exists子句不能包含在or分支中
反聯結
反聯結返回謂語左側的數據行,如果在謂語右側沒有對應的數據行存在的話,它返回在右側的子查詢中沒有匹配的數據行。
--反聯結
SELECT * FROM hr.employees WHERE department_id NOT IN
(SELECT department_id FROM hr.departments WHERE location_id=170)
ORDER BY last_name;
--標準的not in和not exists
SELECT /* not in */ department_name
FROM hr.departments dept
WHERE department_id NOT IN (SELECT department_id FROM hr.employees emp);
SELECT /* not exist */ department_name
FROM hr.departments dept
WHERE NOT EXISTS (SELECT NULL FROM hr.employees emp WHERE dept.department_id=emp.department_id);
如果向not in運算符返回了一個空值,則整個查詢不會返回任何記錄。not in運算符就是!=ANY。
假設你的需求是即使子查詢返回空值也返回相應記錄,可以有下面的這些選擇。
- 在子查詢所返回的列上應用一個nvl函數
- 在子查詢中加上is not null謂語
- 實現not null約束
- 不使用not in(使用不需要關心空值的not exists形式)
--避免not in中的空值
SELECT /* in with nvl */ department_name
FROM hr.departments dept
WHERE department_id NOT IN (SELECT nvl(department_id,-1) FROM hr.employees emp);
SELECT /* in with not null */ department_name
FROM hr.departments dept
WHERE department_id NOT IN (SELECT department_id FROM hr.employees emp WHERE department_id IS NOT NULL);
--not in和not exists的替代寫法
SELECT /* minus */ department_name
FROM hr.departments
WHERE department_id IN (
SELECT department_id FROM hr.departments
MINUS
SELECT department_id FROM hr.employees
);
SELECT /* left outer */ department_name
FROM hr.departments dept LEFT OUTER JOIN
hr.employees emp ON dept.department_id=emp.department_id
WHERE emp.department_id IS NULL;
SELECT /* left outer old (+) */ department_name
FROM hr.departments dept, hr.employees emp
WHERE dept.department_id=emp.department_id(+)
AND emp.department_id IS NULL;
反聯結執行計劃
與半聯結一樣,反聯結也是一種可以應用到嵌套循環聯結,散列聯結或合并聯結的優化方法。同時還要記信它是一種允許當子查詢中找到第一條匹配記錄時停止處理的優化方法。
SQL> --反聯結執行計劃
SQL> SELECT /* not in */ department_name
2 FROM hr.departments dept
3 WHERE dept.department_id NOT IN (SELECT department_id FROM hr.employees emp);
未選定行
執行計劃
----------------------------------------------------------
Plan hash value: 4201340344
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 323 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN ANTI NA | | 17 | 323 | 6 (17)| 00:00:01 |
| 2 | SORT JOIN | | 27 | 432 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 27 | 432 | 2 (0)| 00:00:01 |
| 4 | INDEX FULL SCAN | DEPT_ID_PK | 27 | | 1 (0)| 00:00:01 |
|* 5 | SORT UNIQUE | | 107 | 321 | 4 (25)| 00:00:01 |
| 6 | TABLE ACCESS FULL | EMPLOYEES | 107 | 321 | 3 (0)| 00:00:01 |
SQL> SELECT /* not exists */ department_name
2 FROM hr.departments dept
3 WHERE NOT EXISTS (SELECT NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
已選擇16行。
執行計劃
----------------------------------------------------------
Plan hash value: 3082375452
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 323 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS ANTI | | 17 | 323 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 41 | 123 | 0 (0)| 00:00:01 |
注意,NOT EXISTS語句生成了嵌套循環反聯結(NESTED LOOPS ANTI)執行計劃而not in 語句生成了合并反聯結(MERGE JOIN ANTI NA)執行聯結。
SQL> set echo on
SQL> @F:\bjc-study\sql\flush_pool
alter system flush shared_pool;
SQL> @F:\bjc-study\sql\anti_ex2
set echo on
SELECT /* in */ dept.department_name
FROM hr.departments dept
WHERE dept.department_id IN (SELECT e.department_id FROM hr.employees e);
DEPARTMENT_NAME
------------------------------
Administration
Marketing
Purchasing
Human Resources
Shipping
IT
Public Relations
Sales
Executive
Finance
Accounting
11 rows selected
SELECT /* in with nvl */ department_name
FROM hr.departments dept
WHERE department_id NOT IN (SELECT nvl(department_id,-1) FROM hr.employees emp);
DEPARTMENT_NAME
------------------------------
Treasury
Corporate Tax
Control And Credit
Shareholder Services
Benefits
Manufacturing
Construction
Contracting
Operations
IT Support
NOC
IT Helpdesk
Government Sales
Retail Sales
Recruiting
Payroll
16 rows selected
SELECT /* in with not null */ department_name
FROM hr.departments dept
WHERE department_id NOT IN (SELECT department_id FROM hr.employees emp WHERE department_id IS NOT NULL);
DEPARTMENT_NAME
------------------------------
Treasury
Corporate Tax
Control And Credit
Shareholder Services
Benefits
Manufacturing
Construction
Contracting
Operations
IT Support
NOC
IT Helpdesk
Government Sales
Retail Sales
Recruiting
Payroll
16 rows selected
SELECT /* exists */ dept.department_name
FROM hr.departments dept
WHERE EXISTS(SELECT NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
DEPARTMENT_NAME
------------------------------
Administration
Marketing
Purchasing
Human Resources
Shipping
IT
Public Relations
Sales
Executive
Finance
Accounting
11 rows selected
SQL> set echo off
SQL> set echo on
SQL> @F:\bjc-study\sql\fsp
SELECT DISTINCT s.SQL_ID,
s.CHILD_NUMBER,
s.PLAN_HASH_VALUE plan_hash,
sql_text,--p.OPTIONS,
--DECODE(p.OPTIONS,'SEMI',p.OPERATION||' '||p.OPTIONS,NULL) JOIN
CASE WHEN p.OPTIONS LIKE '%SEMI%' OR p.OPTIONS LIKE '%ANTI%' THEN
p.OPERATION||' '||p.OPTIONS
END JOIN
FROM v$sql s,v$sql_plan p
WHERE s.sql_id=p.sql_id
AND s.CHILD_NUMBER=p.CHILD_NUMBER
AND UPPER(s.SQL_TEXT) LIKE UPPER(NVL('&sql_text','%department%'))
AND s.SQL_TEXT NOT LIKE '%FROM v$sql s%'
AND s.SQL_ID LIKE NVL('&sql_id',s.SQL_ID)
ORDER BY 1,2,3;
SQL_ID CHILD_NUMBER PLAN_HASH SQL_TEXT JOIN
------------- ------------ ---------- -------------------------------------------------------------------------------- --------------------------------------------------------------------------------
38t336psnuanq 0 3082375452 SELECT /* in with not null */ department_name FROM hr.departments dept WHERE dep NESTED LOOPS ANTI
38t336psnuanq 0 3082375452 SELECT /* in with not null */ department_name FROM hr.departments dept WHERE dep
4kwptwjxt4dr1 0 2605691773 SELECT /* exists */ dept.department_name FROM hr.departments dept WHERE EXISTS(S NESTED LOOPS SEMI
4kwptwjxt4dr1 0 2605691773 SELECT /* exists */ dept.department_name FROM hr.departments dept WHERE EXISTS(S
6gw559yrvghu2 0 2605691773 SELECT /* in */ dept.department_name FROM hr.departments dept WHERE dept.departm NESTED LOOPS SEMI
6gw559yrvghu2 0 2605691773 SELECT /* in */ dept.department_name FROM hr.departments dept WHERE dept.departm
93sq6g19gj5ax 0 3822487693 SELECT /* in with nvl */ department_name FROM hr.departments dept WHERE departme MERGE JOIN ANTI
93sq6g19gj5ax 0 3822487693 SELECT /* in with nvl */ department_name FROM hr.departments dept WHERE departme
8 rows selected
SQL> @F:\bjc-study\sql\anti_ex3
SELECT /* not exists */ dept.department_name
FROM hr.departments dept
WHERE NOT EXISTS (SELECT NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
DEPARTMENT_NAME
------------------------------
Treasury
Corporate Tax
Control And Credit
Shareholder Services
Benefits
Manufacturing
Construction
Contracting
Operations
IT Support
NOC
IT Helpdesk
Government Sales
Retail Sales
Recruiting
Payroll
16 rows selected
SELECT /* not in not null */ dept.department_name
FROM hr.departments dept
WHERE dept.department_id NOT IN (SELECT emp.department_id FROM hr.employees emp WHERE emp.department_id IS NOT NULL);
DEPARTMENT_NAME
------------------------------
Treasury
Corporate Tax
Control And Credit
Shareholder Services
Benefits
Manufacturing
Construction
Contracting
Operations
IT Support
NOC
IT Helpdesk
Government Sales
Retail Sales
Recruiting
Payroll
16 rows selected
SELECT /* left outer */ dept.department_name
FROM hr.departments dept LEFT OUTER JOIN
hr.employees emp ON dept.department_id=emp.department_id
WHERE emp.department_id IS NULL;
DEPARTMENT_NAME
------------------------------
Treasury
Corporate Tax
Control And Credit
Shareholder Services
Benefits
Manufacturing
Construction
Contracting
Operations
IT Support
NOC
IT Helpdesk
Government Sales
Retail Sales
Recruiting
Payroll
16 rows selected
SELECT /* left outer old (+) */ dept.department_name
FROM hr.departments dept ,
hr.employees emp WHERE dept.department_id=emp.department_id(+)
AND emp.department_id IS NULL;
DEPARTMENT_NAME
------------------------------
Treasury
Corporate Tax
Control And Credit
Shareholder Services
Benefits
Manufacturing
Construction
Contracting
Operations
IT Support
NOC
IT Helpdesk
Government Sales
Retail Sales
Recruiting
Payroll
16 rows selected
SELECT /* minus */ dept.department_name
FROM hr.departments dept WHERE dept.department_id IN
(
SELECT dept.department_id FROM hr.departments dept
MINUS
SELECT emp.department_id FROM hr.employees emp
);
DEPARTMENT_NAME
------------------------------
Treasury
Corporate Tax
Control And Credit
Shareholder Services
Benefits
Manufacturing
Construction
Contracting
Operations
IT Support
NOC
IT Helpdesk
Government Sales
Retail Sales
Recruiting
Payroll
16 rows selected
SQL> @F:\bjc-study\sql\fsp
SELECT DISTINCT s.SQL_ID,
s.CHILD_NUMBER,
s.PLAN_HASH_VALUE plan_hash,
sql_text,--p.OPTIONS,
--DECODE(p.OPTIONS,'SEMI',p.OPERATION||' '||p.OPTIONS,NULL) JOIN
CASE WHEN p.OPTIONS LIKE '%SEMI%' OR p.OPTIONS LIKE '%ANTI%' THEN
p.OPERATION||' '||p.OPTIONS
END JOIN
FROM v$sql s,v$sql_plan p
WHERE s.sql_id=p.sql_id
AND s.CHILD_NUMBER=p.CHILD_NUMBER
AND UPPER(s.SQL_TEXT) LIKE UPPER(NVL('&sql_text','%department%'))
AND s.SQL_TEXT NOT LIKE '%FROM v$sql s%'
AND s.SQL_ID LIKE NVL('&sql_id',s.SQL_ID)
ORDER BY 1,2,3;
SQL_ID CHILD_NUMBER PLAN_HASH SQL_TEXT JOIN
------------- ------------ ---------- -------------------------------------------------------------------------------- --------------------------------------------------------------------------------
1suh9p55gtnz0 0 3082375452 SELECT /* not exists */ dept.department_name FROM hr.departments dept WHERE NOT NESTED LOOPS ANTI
1suh9p55gtnz0 0 3082375452 SELECT /* not exists */ dept.department_name FROM hr.departments dept WHERE NOT
5k7wv14nwckcu 0 3082375452 SELECT /* left outer */ dept.department_name FROM hr.departments dept LEFT OUTER NESTED LOOPS ANTI
5k7wv14nwckcu 0 3082375452 SELECT /* left outer */ dept.department_name FROM hr.departments dept LEFT OUTER
9t89ttfggqtyb 0 3082375452 SELECT /* not in not null */ dept.department_name FROM hr.departments dept WHERE NESTED LOOPS ANTI
9t89ttfggqtyb 0 3082375452 SELECT /* not in not null */ dept.department_name FROM hr.departments dept WHERE
b5fas38b76scf 0 2972564128 SELECT /* minus */ dept.department_name FROM hr.departments dept WHERE dept.depa
cfgsmt66bfskj 0 3082375452 SELECT /* left outer old (+) */ dept.department_name FROM hr.departments dept , NESTED LOOPS ANTI
cfgsmt66bfskj 0 3082375452 SELECT /* left outer old (+) */ dept.department_name FROM hr.departments dept ,
9 rows selected
控制反聯結的執行計劃
有以下幾個提示可用:
- antijoin--進行反聯結,優化器來決定具體類型
- use_anti--antijoin提示的舊版本
- nl_aj--進行嵌套循環反聯結(自10g起被棄用)
- hash_aj--進行散列反聯結(自10g起被棄用)
- merge_aj--進行合并反聯結(自10g起被棄用)
SQL> set autotrace traceonly exp
SQL> @F:\bjc-study\sql\anti_ex4.sql
SQL> SELECT /* in */ dept.department_name
2 FROM hr.departments dept
3 WHERE dept.department_id NOT IN (SELECT /*+ nl_aj */ e.department_id FROM hr.employees e);
執行計劃
----------------------------------------------------------
Plan hash value: 4201340344
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 323 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN ANTI NA | | 17 | 323 | 6 (17)| 00:00:01 |
| 2 | SORT JOIN | | 27 | 432 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 27 | 432 | 2 (0)| 00:00:01 |
| 4 | INDEX FULL SCAN | DEPT_ID_PK | 27 | | 1 (0)| 00:00:01 |
|* 5 | SORT UNIQUE | | 107 | 321 | 4 (25)| 00:00:01 |
| 6 | TABLE ACCESS FULL | EMPLOYEES | 107 | 321 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("DEPT"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
filter("DEPT"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
SQL>
SQL> SELECT /* exists */ dept.department_name
2 FROM hr.departments dept
3 WHERE NOT EXISTS(SELECT /*+ nl_aj */ NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
執行計劃
----------------------------------------------------------
Plan hash value: 3082375452
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 323 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS ANTI | | 17 | 323 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 41 | 123 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("EMP"."DEPARTMENT_ID"="DEPT"."DEPARTMENT_ID")
在實例級控制反聯結執行計劃
還有一些參數會影響優化器對于反聯結的選擇
- _always_anti_join
- _gs_anti_semi_join_allowed
- _optimizer_null_aware_antijoin
- _optimizer_outer_to_anti_enabled
最主要的需要關注的參數是_always_anti_join,其行為與_always_semi_join相同。
SQL> @F:\bjc-study\sql\flush_pool
alter system flush shared_pool;
System altered
SQL> @F:\bjc-study\sql\anti_ex5.sql
SELECT /* exists */ dept.department_name
FROM hr.departments dept
WHERE NOT EXISTS(SELECT NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
DEPARTMENT_NAME
------------------------------
Treasury
Corporate Tax
Control And Credit
Shareholder Services
Benefits
Manufacturing
Construction
Contracting
Operations
IT Support
NOC
IT Helpdesk
Government Sales
Retail Sales
Recruiting
Payroll
16 rows selected
SELECT /* exists with hint */ dept.department_name
FROM hr.departments dept
WHERE NOT EXISTS(SELECT /*+ hash_aj */ NULL FROM hr.employees emp WHERE emp.department_id=dept.department_id);
DEPARTMENT_NAME
------------------------------
NOC
Manufacturing
Government Sales
IT Support
Benefits
Shareholder Services
Retail Sales
Control And Credit
Recruiting
Operations
Treasury
Payroll
Corporate Tax
Construction
Contracting
IT Helpdesk
16 rows selected
SELECT /* in */ dept.department_name
FROM hr.departments dept
WHERE dept.department_id NOT IN (SELECT e.department_id FROM hr.employees e);
DEPARTMENT_NAME
------------------------------
SELECT /* in */ dept.department_name
FROM hr.departments dept
WHERE dept.department_id NOT IN (SELECT e.department_id FROM hr.employees e);
DEPARTMENT_NAME
------------------------------
SQL> alter session set "_optimizer_null_aware_antijoin"=false;
Session altered
SQL>
SQL> SELECT /* in with AAJ=OFF */ department_name
SELECT /* in with AAJ=OFF */ department_name
2 FROM hr.departments dept
FROM hr.departments dept
3 WHERE dept.department_id NOT IN (SELECT department_id FROM hr.employees emp);
WHERE dept.department_id NOT IN (SELECT department_id FROM hr.employees emp);
DEPARTMENT_NAME
------------------------------
SQL> alter session set "_optimizer_null_aware_antijoin"=true;
Session altered
SQL> set echo off
SQL> @F:\bjc-study\sql\fsp
SQL_ID CHILD_NUMBER PLAN_HASH SQL_TEXT JOIN
------------- ------------ ---------- -------------------------------------------------------------------------------- --------------------------------------------------------------------------------
3hnu1rv8r2nt9 0 3082375452 SELECT /* exists */ dept.department_name FROM hr.departments dept WHERE NOT EXIS NESTED LOOPS ANTI
3hnu1rv8r2nt9 0 3082375452 SELECT /* exists */ dept.department_name FROM hr.departments dept WHERE NOT EXIS
3rv1dbdvj3cjb 0 3587451639 SELECT /* exists with hint */ dept.department_name FROM hr.departments dept WHER HASH JOIN ANTI
3rv1dbdvj3cjb 0 3587451639 SELECT /* exists with hint */ dept.department_name FROM hr.departments dept WHER
f5yk002qrxs94 0 4201340344 SELECT /* in */ dept.department_name FROM hr.departments dept WHERE dept.departm MERGE JOIN ANTI NA
f5yk002qrxs94 0 4201340344 SELECT /* in */ dept.department_name FROM hr.departments dept WHERE dept.departm
fyjby9za2gxqz 0 3416340233 SELECT /* in with AAJ=OFF */ department_name FROM hr.departments dept WHERE dept
7 rows selected
反聯結限制條件
與半聯結一樣,如果子查詢是在where子句的or分支中則不能進行反聯結轉換。
SQL> @F:\bjc-study\sql\anti_ex6.sql
SQL> SET autotrace TRACE EXP;
SQL>
SQL> SELECT /* not in */ department_name
2 FROM hr.departments dept
3 WHERE dept.department_id NOT IN (SELECT department_id FROM hr.employees emp);
執行計劃
----------------------------------------------------------
Plan hash value: 4201340344
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU
)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 323 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN ANTI NA | | 17 | 323 | 6 (17)| 00:00:01 |
| 2 | SORT JOIN | | 27 | 432 | 2 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 27 | 432 | 2 (0)| 00:00:01 |
| 4 | INDEX FULL SCAN | DEPT_ID_PK | 27 | | 1 (0)| 00:00:01 |
|* 5 | SORT UNIQUE | | 107 | 321 | 4 (25)| 00:00:01 |
| 6 | TABLE ACCESS FULL | EMPLOYEES | 107 | 321 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("DEPT"."DEPARTMENT_ID"="DEPARTMENT_ID")
filter("DEPT"."DEPARTMENT_ID"="DEPARTMENT_ID")
SQL>
SQL> SELECT /* nvl */ department_name
2 FROM hr.departments dept
3 WHERE department_id NOT IN (SELECT nvl(department_id,'-10') FROM hr.employees emp);
執行計劃
----------------------------------------------------------
Plan hash value: 3822487693
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 323 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN ANTI | | 17 | 323 | 6 (17)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 27 | 432 | 2 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | DEPT_ID_PK | 27 | | 1 (0)| 00:00:01 |
|* 4 | SORT UNIQUE | | 107 | 321 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL | EMPLOYEES | 107 | 321 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("DEPARTMENT_ID"=NVL("DEPARTMENT_ID",(-10)))
filter("DEPARTMENT_ID"=NVL("DEPARTMENT_ID",(-10)))
反聯結的必要條件
這些必要條件是oracle產生反聯結的最可能的方法:
- 語句必須使用not in(!= all)或not exists
- 語句必須在not in或not exists子句中有一個子查詢
- not in或not exists子句不能包含在or分支中
- not exists子句中的子查詢必須與外層查詢相關
小結:半聯結和反聯結是優化器可以應用到許多常見聯結方法中的選項。這兩個優化選項的基本思想就是將正常的散列,合并或嵌套循環聯結的處理過程變短。在一些情況下,半聯結和反聯結可能極大地提高性能。在很多種構建SQL語句的方法可以使得優化器使用這些選項。最常見的是使用in和exists關鍵字。’