3.4 OR-Expansion Using Function* (NVL, DECODE, RANK To Union All) : NVL, DECODE, RANK 함수를 사용한 조건절을 이용하여 Union All 로 변경하라

1. 선택적 조건절(조건이 들어올 수도 있고 생략될 수도 있음)이 있을 경우 Dynamic SQL을 사용하지 않고 해결할 수 있는 방법
2. _or_expand_nvl_predicate = False (True 설정 시 OR-Expansion Using Function 비 활성화)
3. _optimizer_cost_based_transformation 과 관계 없이 동작

NVL 예제
{code:sqlborderStyle=solid}
var v_loc number;
exec :v_loc := 1500;

SELECT /*+ QB_NAME(MAIN) */
e.employee_id, e.first_name, e.last_name, d.department_name
FROM employee e, department d
WHERE e.department_id = d.department_id
AND d.location_id = NVL (:v_loc, d.location_id); --1500 대입

Plan hash value: 1611409792















--+







---+

IdOperationNameRowsBytesCostTime















--+







---+

0SELECT STATEMENT12
1CONCATENATION
2FILTER
3MERGE JOIN642688600:00:01
4TABLE ACCESS BY INDEX ROWIDDEPARTMENT16320200:00:01
5INDEX FULL SCANDEPT_ID_PK27100:00:01
6SORT JOIN1072354400:00:01
7TABLE ACCESS FULLEMPLOYEE1072354300:00:01
8FILTER
9HASH JOIN15630600:00:01
10TABLE ACCESS BY INDEX ROWIDDEPARTMENT480200:00:01
11INDEX RANGE SCANDEPT_LOCATION_IX4100:00:01
12TABLE ACCESS FULLEMPLOYEE1072354300:00:01















--+







---+

Predicate Information:






--
2 - filter(:V_LOC IS NULL)
4 - filter("D"."LOCATION_ID"=TO_NUMBER(TO_CHAR("D"."LOCATION_ID"))) <<<< 사용하지 않는 조건절 생성됨
6 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
6 - filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
8 - filter(:V_LOC IS NOT NULL)
9 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
11 - access("D"."LOCATION_ID"=TO_NUMBER(:V_LOC))

-- 쿼리가 아래와 같이 UNION-ALL 분리됨 (:v_loc 가 NULL 인 경우와 그렇지 않은 경우)
SELECT e.employee_id, e.first_name, e.last_name, d.department_name
FROM employee e, department d
WHERE e.department_id = d.department_id
AND d.location_id=TO_NUMBER(TO_CHAR(d.location_id)) <<<<<
AND :v_loc IS NULL
UNION ALL
SELECT e.employee_id, e.first_name, e.last_name, d.department_name
FROM employee e, department d
WHERE e.department_id = d.department_id
AND d.location_id = :v_loc
AND :v_loc IS NOT NULL ;

|
||10053 Trace||
|{code:sql}
{code:4 - filter("D"."LOCATION_ID"=TO_NUMBER(TO_CHAR("D"."LOCATION_ID")))}
*********************************
Number of join permutations tried: 2
*********************************
(newjo-save)    [0 1 ]
Transformed predicate "D"."LOCATION_ID"=TO_NUMBER(NVL(:B1,TO_CHAR("D"."LOCATION_ID"))) to 
:B1 IS NOT NULL AND "D"."LOCATION_ID"=TO_NUMBER(:B2) OR :B3 IS NULL AND "D"."LOCATION_ID"=TO_NUMBER(TO_CHAR("D"."LOCATION_ID"))  <<<<< 불필요한 조건 생성

Trying or-Expansion on query block MAIN (#0)

******** Next OR predicate ********

Trying or-Expansion on query block MAIN (#0)

******** OR-branching ********

or-expansion-subheap (create addr=0x0D74C7C8)

-- NVL 함수를 OR 조건으로 분리하기 전에 D.LOCATION_ID 컬럼에 TO_CHAR 함수가 적용됨 : FPD 과정에서 TO_CHAR 적용 되었으나 쓸모없음
-- 조건절의 컬럼이 NULL 허용일 경우 원하는 결과가 달라질 수 있다, NULL = NULL 연산 으로 인해 데이터 누락 발생 가능 하므로 아래와 같이 수정 가능

SELECT e.employee_id, e.first_name, e.last_name, d.department_name
  FROM employee e, department d
 WHERE e.department_id = d.department_id 
   AND :v_loc IS NULL
UNION ALL
SELECT e.employee_id, e.first_name, e.last_name, d.department_name
  FROM employee e, department d
 WHERE e.department_id = d.department_id
   AND d.location_id = :v_loc
   AND :v_loc IS NOT NULL ;

|

DECODE 예제
{code:sqlborderStyle=solid}
SELECT /*+ QB_NAME(MAIN) */
e.employee_id, e.first_name, e.last_name, d.department_name
FROM employee e, department d
WHERE e.department_id = d.department_id
AND d.location_id = DECODE (:v_loc, NULL, d.location_id, :v_loc);

































-

IdOperationNameStartsA-RowsA-TimeBuffersOMem1MemUsed-Mem

































-

0SELECT STATEMENT14500:00:00.0112
1CONCATENATION14500:00:00.0112
  • 2
FILTER1000:00:00.010
  • 3
HASH JOIN0000:00:00.010915K915K
4TABLE ACCESS BY INDEX ROWIDDEPARTMENT0000:00:00.010
  • 5
INDEX FULL SCANDEPT_LOCATION_IX0000:00:00.010
6TABLE ACCESS FULLEMPLOYEE0000:00:00.010
  • 7
FILTER14500:00:00.0112
8NESTED LOOPS14500:00:00.0112
9NESTED LOOPS14500:00:00.017
10TABLE ACCESS BY INDEX ROWIDDEPARTMENT1100:00:00.013
  • 11
INDEX RANGE SCANDEPT_LOCATION_IX1100:00:00.012
  • 12
INDEX RANGE SCANEMP_DEPARTMENT_IX14500:00:00.014
13TABLE ACCESS BY INDEX ROWIDEMPLOYEE454500:00:00.015

































-

Predicate Information (identified by operation id):













---

2 - filter(TO_CHAR(:V_LOC) IS NULL)
3 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
5 - filter("D"."LOCATION_ID" IS NOT NULL)
7 - filter(TO_CHAR(:V_LOC) IS NOT NULL)
11 - access("D"."LOCATION_ID"=:V_LOC)
12 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

– NVL 예제와 다른 점은 :v_loc 가 NULL 일 때, "5 - filter("D"."LOCATION_ID" IS NOT NULL)" 조건이 추가 됨에 따라 D.LOCATION_ID 값이 NULL 인 데이터가 존재 한다면 결과가 달라지게 됨

|

||RANK  예제||
|{code:sql|borderStyle=solid}
SQL> var v_top_rank number;
SQL> exec :v_top_rank := 1;

SELECT /*+ GATHER_PLAN_STATISTICS FIRST_ROWS(1) */
       *
  FROM (SELECT cust_id, amount_sold,
               RANK () OVER (ORDER BY amount_sold DESC) AS amt
          FROM sales)
 WHERE amt <= :v_top_rank; --> 변수에 1 대입

-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name         | Starts | A-Rows |   A-Time   |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |              |      1 |    108 |00:00:00.01 |       |       |          |
|*  1 |  VIEW                                |              |      1 |    108 |00:00:00.01 |       |       |          |
|*  2 |   WINDOW NOSORT STOPKEY              |              |      1 |    108 |00:00:00.01 | 73728 | 73728 |          |
|   3 |    TABLE ACCESS BY GLOBAL INDEX ROWID| SALES        |      1 |    109 |00:00:00.01 |       |       |          |
|   4 |     INDEX FULL SCAN DESCENDING       | IDX_SALES_01 |      1 |    109 |00:00:00.01 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("AMT"<=:V_TOP_RANK)
   2 - filter(RANK() OVER ( ORDER BY INTERNAL_FUNCTION("AMOUNT_SOLD") DESC )<=:V_TOP_RANK)

-- 결과는 108건 이지만 Id 4 에서 109건 읽음, FPD 기능이 분석 함수를 사용할 때도 수행 되고 있으나, 의도한 Plan이 아니며 아래와 같이 기대 했었음

SELECT *
  FROM (SELECT /*+ INDEX(sales IDX_SALES_01) */ cust_id, amount_sold,
               RANK() OVER (ORDER BY amount_sold DESC) AS amt
	      FROM sales
		 WHERE :v_top_rank < :SOME_VALUE --> 오라클이 변수에 적당한 값을 대입하게 된다.
		UNION ALL
		SELECT /*+ FULL(sales) */ cust_id, amount_sold,
               RANK() OVER (ORDER BY amount_sold DESC) AS amt
	      FROM sales
		 WHERE :v_top_rank < :SOME_VALUE) A
 WHERE amt <= :v_top_rank;

 -- 오라클의 문서에 따르면 위의 변환이 가능 하다고 하나 데이터 건수를 늘리거나, 히스토그램 생성, 파라미터 변경 테스트를 했으나 위와 같이 동작하지 않음
 -- Query Optimization with Switch Predicates (Mohamed Zauddin 외 1명) : Patent No : US 6581055 B1 (특허, 2003년)

|