【DB筆試面試613】在Oracle中,和子查詢相關的查詢轉換有哪些?
- 2019 年 10 月 10 日
- 筆記
♣
題目部分
在Oracle中,和子查詢相關的查詢轉換有哪些?
♣
答案部分
(一)子查詢推進(Push Subquery)示例
LHR@orclasm > set serveroutput on LHR@orclasm > exec sql_explain('SELECT /*+ no_push_subq(@lhr_ps)*/ * FROM SYS.TAB$ A WHERE A.ANALYZETIME > (SELECT /*+qb_name(lhr_ps)*/ MAX(B.ANALYZETIME) FROM SYS.IND$ B)','outline'); Plan hash value: 553156288 ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 3322 | 454K| 1118 (1)| 00:00:14 | |* 1 | FILTER | | | | | | | 2 | TABLE ACCESS FULL | TAB$ | 3322 | 454K| 559 (1)| 00:00:07 | | 3 | SORT AGGREGATE | | 1 | 7 | | | | 4 | TABLE ACCESS FULL| IND$ | 5545 | 38815 | 559 (1)| 00:00:07 | ---------------------------------------------------------------------------- Outline Data ------------- /*+ BEGIN_OUTLINE_DATA FULL(@"LHR_PS" "B"@"LHR_PS") FULL(@"SEL$1" "A"@"SEL$1") OUTLINE(@"LHR_PS") OUTLINE_LEAF(@"SEL$1") OUTLINE_LEAF(@"LHR_PS") ALL_ROWS DB_VERSION('11.2.0.3') OPTIMIZER_FEATURES_ENABLE('11.2.0.3') IGNORE_OPTIM_EMBEDDED_HINTS END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("A"."ANALYZETIME"> (SELECT /*+ NO_PUSH_SUBQ QB_NAME ("LHR_PS") */ MAX("B"."ANALYZETIME") FROM "SYS"."IND$" "B")) PL/SQL procedure successfully completed. LHR@orclasm > exec sql_explain('SELECT * FROM SYS.TAB$ A WHERE A.ANALYZETIME > (SELECT MAX(B.ANALYZETIME) FROM SYS.IND$ B)','outline'); Plan hash value: 243387038 ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 166 | 23240 | 1118 (1)| 00:00:14 | |* 1 | TABLE ACCESS FULL | TAB$ | 166 | 23240 | 559 (1)| 00:00:07 | | 2 | SORT AGGREGATE | | 1 | 7 | | | | 3 | TABLE ACCESS FULL| IND$ | 5545 | 38815 | 559 (1)| 00:00:07 | ---------------------------------------------------------------------------- Outline Data ------------- /*+ BEGIN_OUTLINE_DATA FULL(@"SEL$2" "B"@"SEL$2") PUSH_SUBQ(@"SEL$2") FULL(@"SEL$1" "A"@"SEL$1") OUTLINE_LEAF(@"SEL$1") OUTLINE_LEAF(@"SEL$2") ALL_ROWS DB_VERSION('11.2.0.3') OPTIMIZER_FEATURES_ENABLE('11.2.0.3') IGNORE_OPTIM_EMBEDDED_HINTS END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("A"."ANALYZETIME"> (SELECT MAX("B"."ANALYZETIME") FROM "SYS"."IND$" "B")) PL/SQL procedure successfully completed.
(二)子查詢展開(Subquery Unnesting)
--1)IN和EXISTS轉換為半連接(SEMI JOIN): CREATE TABLE EMP_LHR AS SELECT * FROM SCOTT.EMP; CREATE TABLE DEPT_LHR AS SELECT * FROM SCOTT.DEPT; SELECT * FROM EMP_LHR A WHERE EXISTS (SELECT 1 FROM DEPT_LHR B WHERE B.DEPTNO=A.DEPTNO); ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 14 | 1400 | 7 (15)| 00:00:01 | |* 1 | HASH JOIN SEMI | | 14 | 1400 | 7 (15)| 00:00:01 | | 2 | TABLE ACCESS FULL| EMP_LHR | 14 | 1218 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| DEPT_LHR | 4 | 52 | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------- --子查詢引用表DEPT,最終轉換為兩個表的哈希半連接。也就是說,EXISTS子句中的子查詢被展開,其中的對象與主查詢中的對象直接進行半關聯操作。IN的情況類似,如下: SELECT * FROM EMP_LHR A WHERE A.DEPTNO IN (SELECT B.DEPTNO FROM DEPT_LHR B); ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 14 | 1400 | 7 (15)| 00:00:01 | |* 1 | HASH JOIN SEMI | | 14 | 1400 | 7 (15)| 00:00:01 | | 2 | TABLE ACCESS FULL| EMP_LHR | 14 | 1218 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| DEPT_LHR | 4 | 52 | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------- --2)NOT IN和NOT EXISTS轉換為反連接(ANTI JOIN): SELECT * FROM EMP_LHR A WHERE NOT EXISTS (SELECT 1 FROM DEPT_LHR B WHERE B.DEPTNO=A.DEPTNO); ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 14 | 1400 | 7 (15)| 00:00:01 | |* 1 | HASH JOIN ANTI | | 14 | 1400 | 7 (15)| 00:00:01 | | 2 | TABLE ACCESS FULL| EMP_LHR | 14 | 1218 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| DEPT_LHR | 4 | 52 | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------- --優化器將NOT EXISTS後的子查詢做解嵌套,然後選擇了哈希的反連接。這種轉換屬於基於代價的查詢轉換。下面看看NOT IN的情況: SELECT * FROM EMP_LHR A WHERE A.DEPTNO NOT IN (SELECT B.DEPTNO FROM DEPT_LHR B); ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 14 | 1400 | 7 (15)| 00:00:01 | |* 1 | HASH JOIN ANTI NA | | 14 | 1400 | 7 (15)| 00:00:01 | | 2 | TABLE ACCESS FULL| EMP_LHR | 14 | 1218 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| DEPT_LHR | 4 | 52 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------------
和NOT EXISTS類似,也選擇了哈希連接,只不過是HASH JOIN ANTI NA。這裡的NA,實際表示Null-Aware的意思,在11g及以後的版本中,Oracle增加了對空值敏感的反關聯的支援。
(三)子查詢合併(Subquery Coalesce)
LHR@orclasm > SELECT * FROM EMP_LHR A WHERE EXISTS (SELECT 1 FROM DEPT_LHR B WHERE B.DEPTNO=A.DEPTNO AND B.DEPTNO=10) AND EXISTS (SELECT 1 FROM DEPT_LHR B WHERE B.DEPTNO=A.DEPTNO AND B.DEPTNO=20) ; no rows selected Execution Plan ---------------------------------------------------------- Plan hash value: 3115025369 --------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 113 | 0 (0)| | |* 1 | FILTER | | | | | | |* 2 | HASH JOIN SEMI | | 1 | 113 | 10 (10)| 00:00:01 | |* 3 | HASH JOIN SEMI | | 1 | 100 | 7 (15)| 00:00:01 | |* 4 | TABLE ACCESS FULL| EMP_LHR | 1 | 87 | 3 (0)| 00:00:01 | |* 5 | TABLE ACCESS FULL| DEPT_LHR | 1 | 13 | 3 (0)| 00:00:01 | |* 6 | TABLE ACCESS FULL | DEPT_LHR | 1 | 13 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter(NULL IS NOT NULL) 2 - access("B"."DEPTNO"="A"."DEPTNO") 3 - access("B"."DEPTNO"="A"."DEPTNO") 4 - filter("A"."DEPTNO"=10 AND "A"."DEPTNO"=20) 5 - filter("B"."DEPTNO"=20 AND "B"."DEPTNO"=10) 6 - filter("B"."DEPTNO"=10 AND "B"."DEPTNO"=20)
在這個查詢語句中,外部查詢要滿足兩個子查詢—SUB1和SUB2,但兩者條件不同,不能簡單合併。因此在執行計劃中,分別對兩者進行了掃描(直觀感覺就是對DEPT_LHR進行了兩次掃描),然後再做關聯查詢。
LHR@orclasm > SELECT * FROM EMP_LHR A WHERE EXISTS (SELECT 1 FROM DEPT_LHR B WHERE B.DEPTNO=A.DEPTNO AND B.DEPTNO=10) AND EXISTS (SELECT 1 FROM DEPT_LHR B WHERE B.DEPTNO=A.DEPTNO) ; EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ---------- ---------- --------- ---------- ------------------- ---------- ---------- ---------- 7934 MILLER CLERK 7782 1982-01-23 00:00:00 1300 10 7839 KING PRESIDENT 1981-11-17 00:00:00 5000 10 7782 CLARK MANAGER 7839 1981-06-09 00:00:00 2450 10 Execution Plan ---------------------------------------------------------- Plan hash value: 3403691855 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 100 | 7 (15)| 00:00:01 | |* 1 | HASH JOIN SEMI | | 1 | 100 | 7 (15)| 00:00:01 | |* 2 | TABLE ACCESS FULL| EMP_LHR | 3 | 261 | 3 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL| DEPT_LHR | 1 | 13 | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("B"."DEPTNO"="A"."DEPTNO") 2 - filter("A"."DEPTNO"=10) 3 - filter("B"."DEPTNO"=10)
在這個查詢中,外部對EMP_LHR表的查詢要同時滿足SUB1和SUB2兩個子查詢,而SUB1在語義上又是SUB2的子集,因此優化器將兩個子查詢進行了合併(只進行一次對DEPT_LHR表的掃描),然後與外部表EMP_LHR進行半連接。
本文選自《Oracle程式設計師面試筆試寶典》,作者:李華榮。