본문 바로가기

데이터베이스/SQL 튜닝

[SQL튜닝/옵티마이저 원리] 쿼리 변환 2 (조건절 Pushing, 조건절 이행, 불필요한 조인 제거, OR 조건을 Union으로 변환, 기타)

🖥️ 들어가며

📌 쿼리 변환 종합
- 서브쿼리 Unnesting (+ push_subq)
- 뷰 Merging (불가한 경우: 집CR 함수는 불가, Group By는 가능)
- 조건절 Pushing (조인절 Pushdown, 조인절 Pullup, 조인조건 Pushdown)
- 조건절 이행
- 불필요한 조인 제거
- OR 조건을 Union으로 변환 (OR-Expansion)
- 기타

 


✏️ 1. 조건절 Pushing

  • 옵티마이저의 뷰 처리
    • 1차적으로 뷰 Merging을 수행하지만
    • 뷰 Merging 실패 시 옵티마이저는 조건절(Predicate) Pushing을 시도합니다.
  • 조건절 Pushing
    • 뷰를 참조하는 메인쿼리 블록의 조건절을 → 뷰 쿼리 블록 안으로 Pushing하는 기능입니다.
  • 조건절 Pushing 종류
    • 1. 조건절 Pushdown: 쿼리 블록 밖에 있는 조건을 → 쿼리 블록 안쪽으로 밀어넣는 것입니다.
    • 2. 조건절 Pullup: 쿼리 블록 안에 있는 조건을 → 쿼리 블록 밖으로 꺼내오는 것입니다.
      (다리 다른 쿼리블록 안으로 Pushdown 하는 데 활용 가능)
    • 3. 조인 조건 Pushdown: NL 조인 수행 중 Outer의 조건을 Inner쪽 뷰 쿼리 안으로 밀어 넣는 것입니다.
  • 조건절 Pushing 불가 조건
    • 뷰 안에 ROWNUM, 분석함수가 있을 경우 Push가 불가합니다.
  • 관련 힌트와 파라미터
    • select /*+ opt_param('_optimizer_push_pred_cost_based', 'false') */ * from ...
    • alter system set "_optimizer_push_pred_cost_based" = false;

 

 

 

✏️ 1-1. 조건절(Predicate) Pushdown

  • Group By 절을 포함한 뷰 - 조건절 Pushdown (1)
    • NOSORT, TABLE ACCESS BY INDEX ROWID
      : 뷰 Merging에 실패했지만, 옵티마이저가 조건절을 뷰 안쪽으로 밀어넣어 인덱스가 스캔한 것을 알 수 있습니다.
    • access("DEPTNO"=30)
      : 만약 조건절 Pushdown이 동작하지 않았다면 필터링 조건으로 dept = 30이 실행되었을 것입니다.
select deptno, avg_sal
  from (select /*+no_merge */ deptno, avg(sal) avg_sal from emp group by deptno) a
 where deptno = 30
 
--------------------------------------------------------------
| Id  | Operation                     | Name        | E-Rows |
--------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |        |
|   1 |  VIEW                         |             |      1 | --- View
|   2 |   SORT GROUP BY NOSORT        |             |      1 | --- Nosort
|   3 |    TABLE ACCESS BY INDEX ROWID| EMP         |      6 |
|*  4 |     INDEX RANGE SCAN          | EMP_DEEPTNO |      6 |
--------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("DEPTNO"=30) --- Filter가 아닌 Access

 

  • 조건절 Pushdown (2)
    • b.deptno = 30 조건이 인라인 뷰 a로 푸쉬 → a의 결과를 필터링하는 데 사용되었음을 보여줍니다.
select /*+ no_merge(a)*/
       b.deptno, b.dname, a.avg_sal
from   (select deptno, avg(sal) avg_sal from emp group by deptno) a
       , dept b
where  a.deptno = b.deptno
and    b.deptno = 30
   
------------------------------------------------------------------------------------------
| Id  | Operation                      | Name        | E-Rows |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |             |        |       |       |          |
|   1 |  NESTED LOOPS                  |             |      1 |       |       |          |
|   2 |   TABLE ACCESS BY INDEX ROWID  | DEPT        |      1 |       |       |          |
|*  3 |    INDEX UNIQUE SCAN           | DEPT_PK     |      1 |       |       |          |
|   4 |   VIEW                         |             |      1 |       |       |          |
|   5 |    SORT GROUP BY               |             |      1 |  2048 |  2048 | 2048  (0)|
|   6 |     TABLE ACCESS BY INDEX ROWID| EMP         |    118 |       |       |          |
|*  7 |      INDEX RANGE SCAN          | EMP_DEEPTNO |    118 |       |       |          |
------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access("B"."DEPTNO"=30)
   7 - access("DEPTNO"=30)

 

 

 

✏️ 1-2. 조건절(Predicate) Pullup

  • 쿼리 블록 안에 있는 조건을 → 쿼리 블록 밖으로 꺼내오는 것입니다.
  • 조건절 Pullup (1)
    • e1의 deptno = 1의 조건이 e2의 액세스 조건으로 사용되었습니다.
    • 최적화된 쿼리: 조건절 Pullup 적용
      • 5, 9: 두 개의 인라인뷰(e1, e2)가 모두 deptno = 10 조건을 포함하고 있습니다.
    • 비효율적인 쿼리: Pullup 비활성화
      • _pred_move_around 파라미터를 flase로 설정하여 조건절 Pullup 비할성화 했습니다.
      • 그 결과, e2 뷰에서 deptno = 10 조건이 외부로 끌어올려지지 않았습니다.
      • 7-10: deptno = 10 조건이 적용되지 않아 모든 부서의 데이터를 처리합니다.
      • filter(COUNT(*) >0): 불필요한 추가 필터링 단계입니다.
-- 최적화된 쿼리: 조건절 Pullup 적용
select * from  
  (select deptno, avg(sal) from emp where deptno = 10 group by deptno) e1
 ,(select deptno, min(sal), max(sal) from emp group by deptno) e2
where  e1.deptno = e2.deptno ;

---------------------------------------------------------------------------------------------
| Id  | Operation                      | Name           | E-Rows |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                |        |       |       |          |
|*  1 |  HASH JOIN                     |                |      1 |   963K|   963K|  405K (0)|
|   2 |   VIEW                         |                |      1 |       |       |          |
|   3 |    HASH GROUP BY               |                |      1 |   899K|   899K|  466K (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID| EMP            |     98 |       |       |          |
|*  5 |      INDEX RANGE SCAN          | EMP_DEPTNO_IDX |     98 |       |       |          |
|   6 |   VIEW                         |                |      1 |       |       |          |
|   7 |    HASH GROUP BY               |                |      1 |   838K|   838K|  465K (0)|
|   8 |     TABLE ACCESS BY INDEX ROWID| EMP            |     98 |       |       |          |
|*  9 |      INDEX RANGE SCAN          | EMP_DEPTNO_IDX |     98 |       |       |          |
---------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("E1"."DEPTNO"="E2"."DEPTNO")
   5 - access("DEPTNO"=10)
   9 - access("DEPTNO"=10)
   
-- 비효율적인 쿼리: Pullup 비활성화
select /*+ opt_param('_pred_move_around', 'false') */ * from  --- 파라미터 false 설정
  (select deptno, avg(sal) from emp where deptno = 10 group by deptno) e1
 ,(select deptno, min(sal), max(sal) avg_sal from emp group by deptno) e2
where  e1.deptno = e2.deptno;

----------------------------------------------------------------------------------------------
| Id  | Operation                       | Name           | E-Rows |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                |        |       |       |          |
|   1 |  NESTED LOOPS                   |                |      1 |       |       |          |
|   2 |   VIEW                          |                |      1 |       |       |          |
|   3 |    HASH GROUP BY                |                |      1 |   899K|   899K|  728K (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID | EMP            |     98 |       |       |          |
|*  5 |      INDEX RANGE SCAN           | EMP_DEPTNO_IDX |     98 |       |       |          |
|   6 |   VIEW PUSHED PREDICATE         |                |      1 |       |       |          |
|*  7 |    FILTER                       |                |        |       |       |          |
|   8 |     SORT AGGREGATE              |                |      1 |       |       |          |
|   9 |      TABLE ACCESS BY INDEX ROWID| EMP            |     98 |       |       |          |
|* 10 |       INDEX RANGE SCAN          | EMP_DEPTNO_IDX |     98 |       |       |          |
----------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("DEPTNO"=10)
   7 - filter(COUNT(*)>0)
  10 - access("DEPTNO"="E1"."DEPTNO")

 

 

 

✏️ 1-3. 조인조건(Join Predicate) Pushdown

  • NL 조인 수행 중 Outer의 조건을 Inner쪽 뷰 쿼리 안으로 밀어 넣는 것입니다.
  • 조인조건절 Pusdown 제어 힌트
    • push_pred: 조인조건 Pushdown을 유도합니다.
    • no_push_pred: 조인조건 Pushdown을 방지합니다.
  • 제어 파라미터
    • _push_join_predicate: 뷰 Merging에 실패한 뷰 안쪽으로, 조인조건을 Pushdown하는 기능을 활성화합니다.
    • _push_join_union_view: Union all을 포함하는 Non-mergeable view 안쪽으로, 조인조건을 Pushdown하는 기능을 활성화합니다.
    • _push_join_union_view2: Union을 포함하는 Non-mergeable view 안쪽으로, 조인조건을 Pushdown하는 기능을 활성화합니다.
  • 조인조건 Pushdown (1)
    • push_pred(e) 힌트: 조인조건을 인라인 뷰 내부로 밀어넣도록 옵티마이저에게 지시합니다.
    • VIEW PUSHED PREDICATE: 조인조건이 뷰 내부로 푸쉬되었음을 안내합니다.
    • access("DEPTNO" = "D"."DEPTNO"): emp 내부 인덱스 스캔 + 인라인 뷰 내부에서 조인조건이 적용되었음을 보여줍니다.
select /*+ no_merge(e) push_pred(e) */ *
from   dept d, (select empno, ename, deptno from emp) e
where  e.deptno(+) = d.deptno
and    d.loc = 'CHICAGO';

-----------------------------------------------------------------
| Id  | Operation                     | Name           | E-Rows |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT              |                |        |
|   1 |  NESTED LOOPS OUTER           |                |     98 |
|*  2 |   TABLE ACCESS FULL           | DEPT           |      1 |
|   3 |   VIEW PUSHED PREDICATE       |                |      1 |
|   4 |    TABLE ACCESS BY INDEX ROWID| EMP            |     98 |
|*  5 |     INDEX RANGE SCAN          | EMP_DEPTNO_IDX |     98 |
-----------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("D"."LOC"='CHICAGO')
   5 - access("DEPTNO"="D"."DEPTNO")
  • 조인조건 Pushdown (2)
    • push_pred(e) 힌트: 옵티마이저에게 조인조건을 인라인 뷰 내부로 밀어넣도록 지시합니다.
    • 조인조건: e.deptno = d.deptno → 이 조건이 인라인 뷰 내부로 pushdown → emp 테이블에서 필요한 데이터만 추출합니다.
      • 이로 인해 그룹화, 집계 연산을 수행하기 전 데이터 양을 줄일 수 있습니다.
      • 결과적으로 처리해야 할 데이터 양이 감소해 쿼리 성능이 향상됩니다.
    • 실행계획: ix_emp_deptno_sal 인덱스에 대한 Range Scan이 발생합니다.
      이는 조인조건이 뷰 내부로 Pushdown 되어 인덱스를 효과적으로 사용할 수 있게 되었음을 나타냅니다.
select /*+ leading(d) use_nl(e) push_pred(e) */ *
from   (select /*+ no_merge */ deptno, sum(sal) sal
        from   emp
        -- where deptno = d.deptno 이것과 동일한 효과 (풀면 에러남)
        group by deptno
        ) e, dept d
where   d.loc = 'New York'
and     e.deptno = d.deptno;

-- Id: 6  | Operation: INDEX RANGE SCAN    | Name: IX_EMP_DEPTNO_SAL
-- Rows: 4| Bytes: 28 | Cost (%CPU): 1 (0) | Time: 00:00:01
  • 조인조건 Pushdown과 LATERAL IN-LINE VIEW
    • LATERAL: Oracle 12c에서 도입된 기능, 상관 서브쿼리를 from절에서 인라인 뷰처럼 사용할 수 있게 해 주는 키워드입니다.
      • dept 테이블과 emp 테이블을 조인할 때 LATERAL 키워드를 사용합니다.
      • LATERAL 내부 서브쿼리에서 where deptno = d.deptno과 같이 외부테이블(dept) 컬럼을 직접 참조합니다.
select *
from   dept d,
       lateral (select   /*+ no_merge */ deptno, sum(sal) sal
                from     emp
                where    deptno = d.deptno
                group by deptno
                ) e
where  d.loc = 'New York';

 

 

 

✏️ 2. 조건절 이행

  • 연역법 추리를 통해 새로운 조건절을 내부적으로 생성해 주는 쿼리입니다.
  • 연역법 추리: A=B이고, B=C이면, A=C이다
  • 조건절 이행 (1)
    • access("D"."DEPTNO"=10): 조건에 없던 d.deptno 값이 추가된 이유는 다음과 같습니다.
      1) e.deptno = 10이고 → 2) d.deptno = e.deptno 이면 → 3) d.deptno = 10 라는 결론
select * from dept d, emp e
where e.job='MANAGER'
and e.deptno=10
and d.deptno=e.deptno;

Execution Plan
----------------------------------------------------------
Plan hash value: 35793385

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    57 |     2   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |         |     1 |    57 |     2   (0)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    20 |     1   (0)| 00:00:01 |
|*  3 |    INDEX UNIQUE SCAN         | PK_DEPT |     1 |       |     0   (0)| 00:00:01 |
|   4 |   TABLE ACCESS BY INDEX ROWID| EMP     |     1 |    37 |     1   (0)| 00:00:01 |
|*  5 |    INDEX RANGE SCAN          | EMP_IDX |     2 |       |     0   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("D"."DEPTNO"=10) --- 조건에 없던 d.deptno 값 추가
   5 - access("E"."DEPTNO"=10 AND "E"."JOB"='MANAGER')

 

 

 

 

✏️ 3. 불필요한 조인 제거

  • 1:M 관계인 두 테이블을 조인하는 쿼리문에서. 조인문을 제외한 어디에서도 1쪽 테이블을 참조하지 않는다면?
  • 옵티마이저는 이 특성을 이용해 M쪽 테이블만 읽도록 쿼리를 변환하는 기능입니다. (10g부터 작동)
  • 조인 제거를 위한 조건
    • PK 존재: 조인을 하려는 두 테이블간 조인 카디널리티를 파악하기 위해 필수입니다.
    • FK 존재: Null을 제외한 레코드 중 조인이 실패하는 레코드가 없다는 것을 보장해야 합니다.
      (Null에 대한 처리를 위해 옵티마이저 내부적으로 IS NOT NULL 조건을 추가)
  • 조인 제거 관련 파리미터 및 힌트 (조인 제거 기능 ON/OFF)
    • alter session set "_optimizer_join_elimination_enabled" = true;
    • /*+ eliminate_join(d) */
    • /*+ no_eliminate_join(d) */
  • 조인제거 예시 (1) 등치조인
    • 1:M 관계에서 1쪽 테이블을 읽지 않고
    • dept에 pk 제약조건 + emp에 fk(deptno) 제약조건이 존재하며
    • 1쪽 테이블 집합 Join이 불필요하다면 => "불필요한 조인 제거"
select e.empno, e.ename, e.deptno, e.sal, e.hiredate
from   dept d, emp e
where  d.deptno = e.deptno;
  • 조인제거 예시 (2) M쪽 Outer 조인 시 조인제거 (11g부터 기능 구현)
    • 1:M 관계에서 1쪽 테이블을 읽지 않고
    • 1쪽 테이블 집합 Join이 불필요하다면 => "불필요한 조인 제거"
    • Outer Join시 조인제거는 11g부터 기능 구현
      +) FK 및 emp.deptno에 Not null 제약 조건이 없어도 가능합니다.
select e.empno, e.ename, e.sal, e.hiredate
from   emp e, dept d
where  d.deptno(+) = e.deptno;

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |    14 |   588 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| EMP  |    14 |   588 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------

 

 

 

✏️ 4. OR 조건을 Union으로 변환 (OR-Expansion)

  • OR 연산자나 IN 연산자를 사용했을 때,
    내부적으로 Concatnation 실행계획으로 처리되어 마치 2개 쿼리로 나누어져서 실행되는 쿼리 변환 기능입니다.
  • 제어힌트
    • use_concat: OR-Expansion을 유도합니다.
    • ⭐⭐⭐ no_expand: OR-Expansion을 방지합니다.
    • alter session set "_no_or_expansion"=true;로도 설정 가능합니다.
  • OR-Expansion (1)
    • 기본 쿼리
      • Table Access Full: 인덱스를 효과적으로 사용하지 못합니다.
    • use_concat
      • index range scan: 분기된 쿼리마다 별도 인덱스 스캔이 이루어집니다.
      • 하지만 emp 테이블의 액세스가 두 번 일어나므로, 중복 액세스되는 영역의 비중이 작을수록 효과적입니다.
        (그 반대의 경우 오히려 쿼리수행 비용 증가 / 따라서 OR-Expansion은 CBO로 작동)
      • concatenation: 실행계획에서 concatenation 연산이 나타납니다.
      • LNNVL 함수: 중복 액세스되더라도 결과 집합에는 중복이 없게 하려고 사용한 함수입니다.
        - job <> 'clerk' 이거나 job IS NULL인 집합만 읽으려는 것
        - 이 함수는 조건식이 False이거나 알 수 없는 값일 때 True를 리턴합니다.
        - LNNVL (1=1): False / LNNVL(1=2): True / LNNVL(Null=1): True
    • no_expand: OR-Expansion 연산 방지, alter session set "_no_or_expansion"=true;로도 설정 가능합니다.
-- 기본 쿼리
select * from emp where job = 'CLERK' or deptno = 20;
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     7 |   259 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMP  |     7 |   259 |     3   (0)| 00:00:01 | -- 인덱스 효과적 X
--------------------------------------------------------------------------

-- use_concat: or 조건을 union all 형태로 변환하도록 지시
select /*+ use_concat */ *
from   emp
where  job = 'CLERK' or deptno = 20;
-----------------------------------------------------------------------------------------------
| Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                |     8 |   304 |     4   (0)| 00:00:01 |
|   1 | ☆CONCATENATION☆            |                |       |       |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| EMP            |     4 |   152 |     2   (0)| 00:00:01 |
|*  3 |  ☆INDEX RANGE SCAN☆        | EMP_JOB_IDX    |     4 |       |     1   (0)| 00:00:01 |
|*  4 |   TABLE ACCESS BY INDEX ROWID| EMP            |     4 |   152 |     2   (0)| 00:00:01 |
|*  5 |  ☆INDEX RANGE SCAN☆        | EMP_DEPTNO_IDX |     5 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("JOB"='CLERK')
   4 - filter(LNNVL("JOB"='CLERK')) -- LNNVL: 중복 제거를 위해 사용
   5 - access("DEPTNO"=20)

-- no_expand: alter session set "no_or_expansion"=true;로도 설정 가능
select /*+ no_expand */ *
from   emp
where  job = 'CLERK' or deptno = 20;
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     7 |   259 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMP  |     7 |   259 |     3   (0)| 00:00:01 | -- 인덱스 효과적 X
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("DEPTNO"=20 OR "JOB"='CLERK')

 

 

 

✏️ 5. 기타 쿼리 변환

  • Count(컬럼명) 시 Null 제외
    • Count(컬럼명) 시 Null은 포함되지 않습니다.
      ⚠️ 단, Count(*) 함수는 Null을 포함한 모든 행 카운트
    • 따라서 조건절에 IS NOT NULL 을 추가하여 결과집합에서 제외한 후 Count 합니다.
-- 기본 쿼리
select count(e.empno), count(d.dname)
from   emp e, dept d
where  d.deptno = e.deptno
and    d.sal <= 2000;

-- 쿼리 변환: Count 시 Null 제외
select count(e.empno), count(d.dname)
from   emp e, dept d
where  d.deptno = e.deptno
and    e.sal <= 2000
and    e.deptno is not null   --- IS NOT NULL 추가
and    d.deptno is not null;  --- IS NOT NULL 추가
  • 비정상적 조건을 위한 필터 조건 추가
    • 정상적 조건: MIN <= MAX
    • 비정상적 조건: MIN > MAN
select *
from   emp
where  sal between :max and :min; --- 2000부터 10? 이상하죠?
  • 조건절 순서 비교
    • 과거 DBMS: 조건절 기술 순서에 따라 실행계획 수립했으나
    • 최신 DBMS: CBO로 최적화하여 결정합니다.
    • 비교 연산을 적게 처리하는 효과가 있습니다.
select *
from   t
where  c1 = 1
and    c2 = 999; --- c2 조건을 먼저 검색하는 것이 유리
C1 먼저 ▶ 1 1 1 1 1 1 1 1 1 (9번)
C2 먼저 991 992 993 994 995 996 997 998 999 ◀
(1번)

 

 


📒 정리하면

  1. 옵티마이저의 뷰 처리: 1차적으로 뷰 Merging 수행 → 실패 시 조건절 Pushing을 시도합니다. (뷰 안에 ROWNUM, 분석함수가 있을 경우 불가)
    1. 조건절 Pushdown: 조건절을 뷰 안으로 밀어넣어 처리합니다.
    2. 조건절 Pullup: 뷰 내부 조건을 외부로 끌어올려 최적화합니다.
    3. 조인조건 Pushdown: NL 조인 시 Outer 조건을 Inner쪽 뷰 쿼리 안으로 밀어넣습니다. (힌트: push_pred)
    4. LATERAL 키워드: 상관 섭즈쿼리를 FROM 절에서 인라인 뷰처럼 사용할 수 있게 합니다. (12c 도입)
  2. 조건절 이행: 연역법을 통해 새로운 조건을 생성합니다. (A=B이고, B=C이면 "A=C이다")
  3. 불필요한 조인 제거: 1:M 관계에서 1쪽 테이블 참조가 불필요할 때 조인을 제거합니다. (PK, FK 존재)
  4. OR-Expansion: OR 조건을 Union으로 변환하여 인덱스 사용을 최적화합니다. (힌트: use_concat ↔ no_expand)
  5. Count(컬럼명) 함수 최적화: Null 값 처리를 위해 IS NOT NULL 조건을 추가합니다.
  6. 비정상적 조건 처리: MIN  > MAX 같은 비정상 조건에 대한 필터를 추가합니다.
  7. 조건절 순서 최적화: CBO 기반 최적화를 통해 효율적인 조건 처리 순서를 결정합니다.

반응형