인덱스 사용 불가능 경우 

   - 인덱스 컬럼 조건걸 가공 

   - 부정형 비교 

   - is null 

     ( is null 결합 컬럼시 다른 조건절이 사용되면 인덱스 사용 가능한 경우도 있음) 


묵시적 형변환 

  - varchar2 컬럼에 숫자 값을 더하거나 빼는 연산을 가하면 내부적으로 숫자형으로 

    형변환이 일어난다. 

  - 숫자형과 문자형이 비교될 때는 숫자형이 우선시 된다. 

    (만약 문자형 컬럼에 숫자로 변환할 수 없는 문자열이 들어 있으면 쿼리 에러) 

  - like로 비교 할 때는 숫자형이 문자형으로 변환 

  - decode(a,b,c,d) decode 함수의 형은 c 인자에 따라 결정

    ( null 일시 varchar2)  


함수 기반 인덱스 

  - 급할시 임시 방편으로 사용함

 인덱스 기본 구조 

  - 리프 노드상의 인덱스 레코드와 테이블 레코드 간에는 1:1 관계

  - 리프 노드상의 키 값과 테이블 레코드 키 값은 서로 일치 

  - 브랜치 노드상의 레코드 개수는 하위 레벨 블록 개수와 일치 

  - 블랜치 노드상의 키 값은 하위 노드가 갖는 값의 범위를 의미  


 rowid 포맷 

  - 제한된 포맷 ( 6 byte) 

      ● 파티션 되지 않은 일반 테이블에 생성한 인덱스 

      ● 파티션된 테이블에 생성한 로컬 파티션 인덱스 


    데이터파일 번호 + 블록 번호 + 로우 번호 

       

  - 확장된 포맷 (10 byte) 

      ● 파티션 테이블에 생성한 글로벌 파티션 인덱스 

      ● 파티션 테이블에 생성한 비파티션 인덱스 


    데이터 오브젝트 번호 + 데이터파일 번호 + 블록 번호 + 로우 번호 

'ORACLE > SQLP' 카테고리의 다른 글

성능고도화 1-3. 다양한 스캔 방식  (0) 2016.12.26
성능고도화 1-2. 인덱스 기본원리  (0) 2016.12.26
2016년 4월달 SQLP 시험 실기  (0) 2016.11.25
SQLP 시험문제 11일차  (0) 2016.11.24
SQLP 시험 문제 7일차  (0) 2016.11.24

http://cafe.naver.com/dbstudydapsqlp/2220

 -- 이곳에서 문제 참조 함 



내 답안) 


select /*+ ordered use_nl(p4) rowid(p4) */ p4.prod_nm, p.prod_id, p3.order_qty 

 from ( select /*+ no_merge ordered use_hash(o) */ p2.rid, o.order_qty 

         from ( select /*+ ordered use_nl(p1) */ p1.m_code, p1.prod_id 

                  from , t_manuf m, t_product p1 

                 where p1.m_code = m.m_code 

                  and m.m_code between 'M00001' and 'M00100' ) p2 

               , order_qty o

          where p2.prod_id = o.prod_id 

           and o.order_dt = '20160412'

           and o.order_qty > 9000 

        ) p3 

        , t_product p4 

 where p3.rid = p4.rowid 

;


-- 참조한 곳의 답안과 약간 다른데 난 실행계획의 hash 까지 좀 표현하려고 in-line view를 한번 더 씀 

-- 그런데 처음에 답을 보지 않고 답안을 작성했을 때 no_merge 힌트를 사용하지는 않았음 

    이 부분은 문제의 소지가 좀 있음 view merging 은 10g 부터 비용 기반 변환이라서 

    힌트를 사용하지 않으면 어떻게 될지 잘 모름 그런데 만약 바깥에 in-line view가 view merging 되면 

    위의 쿼리는 튜닝 되지 않았을 것임 

    그래서 no_merge 힌트는 써주는 것이 맞았을 것 같음 



'ORACLE > SQLP' 카테고리의 다른 글

성능고도화 1-2. 인덱스 기본원리  (0) 2016.12.26
성능고도화 1-1. 인덱스 구조  (0) 2016.12.26
SQLP 시험문제 11일차  (0) 2016.11.24
SQLP 시험 문제 7일차  (0) 2016.11.24
SQLP 시험문제 6일차  (0) 2016.11.24

1. 

 

  select deptno, job, count(*) 

     from emp 

  where rollup (deptno, job) 

  ;



2. 


select deptno, empno, ename, sum(sal) 

 from emp 

group by deptno, empno, ename 

  union all 

select deptno, empno, null, sum(sal) 

 from emp 

group by deptno, empno 

 union all 

select deptno, null, null , sum(sal) 

 from emp 

group by deptno 

;


--이거 하면서 


select deptno, empno, ename, sum(sal) 

 from emp 

group by rollup (deptno, empno, ename) 


;


이것도 내가 예측한 결과와 맞는지 확인해보기 



3. 


create index dept_loc on dept(loc) ;


create index emp_deptno_job on emp(deptno,job) ;


select * 

 from dept d, 

        ( select deptno, empno, ename, job, sal, sal * 1.1 sal2 

           from emp e1 

           where job = 'CLERK' 

          union all 

          select deptno, empno, ename, job, sal, sal * 1.2 sal2 

            from emp e2 

           where job = 'SALESMAN' ) v 

  where v.deptno = d.deptno and d.loc = 'CHICAGO'

  ;


---  이 경우 힌트 없이도 두번째 인덱스를 타는지 확인해보고 

     ( 원래 조건절 push down 에 대한 문제를 내고 싶었던 것 같은데 

       조건절 push down 은 힌트가 원래 없다. 

       그래서 적용이 원래 되어야 하는건데 옵티마이저에 선택에 따라 

       달라질 수 있는지 확인) 


--- /*+ ordered use_nl(v) */ 이 힌트 추가 후 두번째 인덱스를 타는지도 확인 



4. 


select /*+ gather_plan_statistics */ 

       d.deptno, d.dname, v.avg_sal 

 from dept d

      , (select deptno, avg(sal) avg_sal 

          from emp

         where deptno = d.deptno 

         group by deptno ) v 

 where v.deptno (+) = d.deptno 

;


-- 이걸 테스트 하면서 hash join에서의 outer join 과 nl join 에서 outer join에 실행 계획 비교 


5. 



시험문제 7일차 


4. (결과 테스트 해봐야 함) 


select deptno, LTRIM (sys_connect_by_path(ename,','),',') ename 

 from ( select deptno 

               , ename 

               , row_number() over (partition by deptno order by deptno) rnum 

               , count(*) over (partition by deptno ) cnt 

        from emp 

      ) 

 where level = cnt 

 start with rnum = 1

 connect by prior deptno = deptno 

 and prior rnum = rnum - 1

;


-- 답은 이것으로 예상 


select a.ename, b.ename 

 from emp a, 

       ( select mgr, LTRIM (sys_connect_by_path(ename,','),',') ename 

         from ( select  ename

                     , mgr   

                     , row_number() over (partition by mgr order by empno) rnum 

                 from emp 

               ) 

        start with rnum = 1

        connect by prior mgr = mgr 

        and prior rnum = rnum - 1

       ) b 

where a.empno = b.mgr 

;




5. 


select b.aaa, b.saumsal, sum(sumsal) over (order by aaa ) cumulative 

  from ( select a.aaa , sum(a.sal) sumsal 

           from ( select to_char(hiredate, 'yyyy') aaa , sal 

                    from emp

                 ) a

           group by aaa ) b

 ;


or 

 

select b.aaa, b.saumsal, sum(sumsal) over (order by aaa rows between unbounded preceding and current row ) cumulative 

  from ( select a.aaa , sum(a.sal) sumsal 

           from ( select to_char(hiredate, 'yyyy') aaa , sal 

                    from emp

                 ) a

           group by aaa ) b

 ;


--- 그러나 


select to_char(hiredate, 'yyyy') aaa, sal 

 from emp 

group by to_char(hiredate, 'yyyy') 

;


이게 만약 가능하다면 



select b.aaa, b.saumsal, sum(sumsal) over (order by aaa ) cumulative 

  from ( select  to_char(hiredate, 'yyyy') aaa , sum(a.sal) sumsal 

           from emp

         group by to_char(hiredate, 'yyyy') ) b

 ;


이렇게 쿼리 작성이 가능 
( 쿼리를 좀 더 쉽게 구성 할 수 있고 in-line 뷰 사용 횟수도 줄임) 


1. 


 select deptno, sum(sal) 

 from emp 

group by rollup (deptno) 

order by deptno 

;



2. 


select /*+ gather_plan_statistics */ empno, ename, sal, sum(sal) over (order by empno ) 

 from emp  

order by empno 

;


or 


select /*+ gather_plan_statistics */ empno, ename, sal, sum(sal) over (order by empno rows between unbounded preceding and current row ) 

 from emp  

order by empno 

;

(기본적으로 " rows between unbounded preceding and current row " 이 부분은 생략 가능해서) 



3. 


select deptno, empno, sum(sal) salsum, round(avg(sal))salavg

 from emp a 

group b grouping set (deptno,(deptno,empno))

 order by 1,2 


4. 


select deptno, empno, ename, sal

       ,lead(sal,1) over (order by deptno, empno) , lag(sal,1) over (order by deptno, empno)

       ,lead(rownum,1) over (order by deptno, empno) no1, lag(rownum,1) over (order by deptno, empno) no2   

 from emp 

;





--- 튜닝 전 


select b.대리점번호, b.계약상태코드, count(*) as 계약건수 , max(b.계약일자) as 최근계약일 

 from 대리점 a, 계약 b 

 where a.대리점번호 = b.대리점번호 

  and to_char (b.계약일자, 'yyyymm') = :v_date_contract    

  and b.계약상태코드 in (select 코드 from 코드테이블  where 그룹코드 = 'A01')   --- 이 부분이 예측이 어려움  

  and a.업체 =: v_업체코드 

  group by b.대리점번호, b.계약상태코드

  order by b.대리점번호 

  ;

 

select b.deri_no, b.con_code, count(*) as con_count , max(b.con_date) as last_date 

 from deri a, contract2 b 

 where a.deri_no = b.deri_no 

  and to_char (b.con_date, 'yyyymm') = '199511'  

  and b.con_code in (select con_code from code  where group_code = 'A01')     

  and a.co_code ='A590' 

  group by b.deri_no, b.con_code

  order by b.deri_no

  ; 


 

--- 튜닝 후 

     

1) 계약일자가 'yyyymm' 형식으로 입력 될 경우 


 select /*+ ordered use_nl(b) index (a 대리점_x01) index(b 계약_x01)  */ b.대리점번호, b.계약상태코드, count(*) as 계약건수 , max(b.계약일자) as 최근계약일 

 from 대리점 a, ( select /*+ ordered use_nl(d) index(d 계약_x02)  */ d.대리점번호 , d.계약일자, d.계약상태코드 

                 from 코드테이블 c , 계약 d    

                 where c.코드 = d.코드 

                 and c.그룹코드 = 'A01'     

                ) b 

 where a.대리점번호 = b.대리점번호 

  and b.계약일자 = to_date(:v_date_contract,'')    

  and a.업체 =: v_업체코드 

  group by b.대리점번호, b.계약상태코드

  order by b.대리점번호 

  ;




2) 계약일자가 'yyyymmdd' 형식으로 입력 될 경우 


   select /*+ ordered use_nl(b) index (a deri_x01) index(b contract2_x01)  */ 

          b.deri_no, b.con_code, count(*) as con_count , max(b.con_date) as last_date 

     from deri a, ( select /*+ ordered use_nl(d) index(d contract2_x02)  */ d.deri_no , d.con_date, d.con_code

                        from code c , contract2 d    

                        where c.con_code = d.con_code 

                         and c.group_code = 'A01'     

                     ) b 

     where a.deri_no = b.deri_no 

      and to_char (b.con_date, 'yyyymm') = '199511'    

      and a.co_code = 'A590' 

    group by b.deri_no, b.con_code

    order by b.deri_no

     ;





  --- 인덱스 

   

  대리점_pk = 대리점번호 

  대리점_x01 = 업체코드 + 대리점번호  --- 인덱스만 읽고 처리 가능  

  

  계약_pk = 계약번호 

  계약_x01 = 대리점번호 + 계약일자 + 계약상태코드  

  계약_x02 = 계약상태코드     

  

  


  

  create index deri_x01 on deri(co_code, deri_no) ;

  

  create index contract2_x01 on contract2(deri_no, to_char(con_date,'yyyymm') , con_code ) ;

  

  create index contract2_x02 on contract2(con_code) ;

'ORACLE > SQLP' 카테고리의 다른 글

SQLP 시험 문제 7일차  (0) 2016.11.24
SQLP 시험문제 6일차  (0) 2016.11.24
sqlp 19회 실습문제 2번 테스트 테이블 작성  (0) 2016.11.04
SQLP 19회차 실습문제 2번  (0) 2016.11.03
SQLP 19회차 실습 1번 문제  (0) 2016.11.02

--- 대리점 table 


create table 대리점 

 as 

   select round( dbms_random.value(1, 3000) , 0) as 대리점번호, 'a' as 대리점명 , 'b' as 업체코드 , 'c' as 업체명 

    from dual 

    connect by level <= 3000 

;


update deri 

 set co_code = 'A'|| lpad ( round(dbms_random.value(1, 600),0) ,3 ,0) 

 where deri_no > 0


 select count ( distinct(co_code) ) 

  from deri 

;



select avg(count(*)) 

 from deri 

 group by co_code 

 ;


  --- dbms_random 패키지를 사용해서 딱 600개의 코드는 나오지 않지만 그와 근사치로 나온다 

      (저는 distinct count가 596 나왔습니다.) 

      또 그룹 코드당 평균 5개 정도 대리점을 가지고 있다. 


commit ;



---- 코드 테이블 



create table code  

 as 

   select 'a'||to_char ( round( dbms_random.value(1,10) , 0 ) ) as con_code , 'D01' as group_code 

     from dual 

     connect by level <= 10 

    ;

 

    

 update code 

 set group_code = 'A01' 

 where rownum <= 3 

 ;

 

 select *

  from code 

  where group_code = 'A01'

  ;

 

 commit ;

 

 

 --- 계약 테이블 


 create table 계약1 

  as 

    select /*+ 날짜형식 yyyymm */ rownum as 계약번호, dbms_random.value (1, 3000) as 대리점번호

          , 'a'||to_char ( round( dbms_random.value(1,10) , 0 ) ) as 계약상태코드

          , to_date(round(dbms_random.value(1980, 1999),0) || round(dbms_random.value(1,12),0) , 'yyyymm' ) as 계약일자

     from dual 

     connect by level <= 1000000

;



or 


 create table 계약2 

  as 

    select /*+ 날짜형식 yyyymmdd */ rownum as 계약번호, dbms_random.value (1, 3000) as 대리점번호, 'a'||to_char ( round( dbms_random.value(1,10) , 0 ) ) as 계약상태코드

          , to_date(round(dbms_random.value(1980, 1999),0) || lpad(round(dbms_random.value(1,12),0) ,2 ,0) || round(dbms_random.value(1,28),0) , 'yyyymmdd' ) as 계약일자

     from dual 

     connect by level <= 1000000

;



   -- 날짜는 2월달 때문에 28일까지로 맞춤 


'ORACLE > SQLP' 카테고리의 다른 글

SQLP 시험 문제 7일차  (0) 2016.11.24
SQLP 시험문제 6일차  (0) 2016.11.24
sqlp19회 실습문제 2번 테이블명 컬럼명 영어로 바꿈  (0) 2016.11.04
SQLP 19회차 실습문제 2번  (0) 2016.11.03
SQLP 19회차 실습 1번 문제  (0) 2016.11.02



문제 참고 한 곳 


   http://simverse.tistory.com/86

   http://cafe.daum.net/oracleoracle



--- 튜닝 전 


select b.대리점번호, b.계약상태코드, count(*) as 계약건수 , max(b.계약일자) as 최근계약일 

 from 대리점 a, 계약 b 

 where a.대리점번호 = b.대리점번호 

  and to_char (b.계약일자, 'yyyymm') = :v_date_contract    

  and b.계약상태코드 in (select 코드 from 코드테이블  where 그룹코드 = 'A01')   --- 이 부분이 예측이 어려움  

  and a.업체 =: v_업체코드 

  group by b.대리점번호, b.계약상태코드

  order by b.대리점번호 

  ;

 


 

--- 튜닝 후 

     

1)

 select /*+ ordered use_nl(b) index (a 대리점_x01) index(b 계약_x01)  */ b.대리점번호, b.계약상태코드, count(*) as 계약건수 

         , max(b.계약일자) as 최근계약일 

 from 대리점 a, ( select /*+ ordered use_nl(d) index(d 계약_x02)  */ d.대리점번호 , d.계약일자, d.계약상태코드 

                 from 코드테이블 c , 계약 d    

                 where c.코드 = d.코드 

                 and c.그룹코드 = 'A01'     

                ) b 

 where a.대리점번호 = b.대리점번호 

  and b.계약일자 = to_date(:v_date_contract,'')    

  and a.업체 =: v_업체코드 

  group by b.대리점번호, b.계약상태코드

  order by b.대리점번호 

  ;

  

or   


2)

 select /*+ ordered use_nl(b) index (a 대리점_x01) index(b 계약_x01)  */ b.대리점번호, b.계약상태코드, count(*) as 계약건수 

         , max(b.계약일자) as 최근계약일 

 from 대리점 a, ( select /*+ ordered use_nl(d) index(d 계약_x02)  */ d.대리점번호 , d.계약일자, d.계약상태코드 

                 from 코드테이블 c , 계약 d    

                 where c.코드 = d.코드 

                 and c.그룹코드 = 'A01'     

                ) b 

 where a.대리점번호 = b.대리점번호 

  and to_char (b.계약일자, 'yyyymm') = :v_date_contract    

  and a.업체 =: v_업체코드 

  group by b.대리점번호, b.계약상태코드

  order by b.대리점번호 

  ;


  

 조건 1 : 600개 업체, 3000개 대래점 (하나의 업체에 4 ~ 6개의 대리점이 있다고 가정) , 계약 (10만건) 

 조건 2 : A01 에 해당하는 코드는 3개 

 조건 3 : A01 에 해당하는 코드는 향후 더 추가 될 수 있음

 

 

 1) 개선 부분 

 

 1. 계약일자 컬럼 인덱스 사용 못함

    ( 여기서는 상황에 따라서 해결 방법이 달라짐 

         1) 계약일자가 'yyyymm' 형식의 날짜 데이터라면 컬럼이 아닌 비교 값에 to_date를 사용하면 됨 

         2) 만약 계약일자가 'yyyymm'형식이 아니라면 함수 기반 인덱스 생성 ) 

 2. 적절한 인덱스 생성 

 3. 조인 순서는 대리점 테이블 부터 하도록 힌트 사용

 4. in 의 경우 in-line view를 통해서 조인 순서 조절할 수 있도록 함 

 

  대리점_pk = 대리점번호 

  대리점_x01 = 업체코드 + 대리점번호  --- 인덱스만 읽고 처리 가능  

 

 -- <인덱스>

 

  계약_pk = 계약번호 

  계약_x01 = 대리점번호 + (  1) 계약일자  or  2)  to_char(계약일자,'yyyymm')  )+ 계약상태코드  

  계약_x02 = 계약상태코드 


          

 2)  해당 쿼리를 통해 추가로 살펴 본 부분 

 

 1. 대리점 번호 인덱스 생성 후 group by를 하면 order by가 필요 없는가 

    - 위의 쿼리로 테스트 해봤는데 sort order by는 실행계획에 없고 

        sort group by nosort가 실행계획으로 출력 되었다. 

    

    테스트) 

    

    create index emp1_empno_ename on emp1(empno,ename) ;


    select /*+ index(emp1 emp1_empno_ename) */ empno, ename 

     from emp1 

    where empno = 7788

    group by empno, ename

    order by empno

    ;

 

        

 2. 위와 같은 쿼리에서 in-list 에 새로운 테이블을 사용하게 되면 처리가 어떻게 되는가 

     - unnest , view merging 이 일어날 것이라고 생각했는데 

       일단 테스트를 통해서 먼제 확인해보았다. 

       먼저 서브쿼리 테이블을 읽고 서브쿼리와 메인쿼리 테이블과 조인 한 후 

       메인 쿼리에 다른 테이블과 조인을 하였다. 

       

       테스트)

       create table emp1 

         as select *

             from emp 

         ;

       

      select a.empno, a.ename, b.dname, b.loc 

       from emp a, dept b 

      where a.deptno = b.deptno 

       and a.empno in ( select empno 

                       from emp1 

                     where deptno = 10 ) 

      ; 

       


 3. 인덱스 컬럼이 변형되어서 인데스 사용 못한다고 하였는데 복합 인덱스에  두번째 컬럼일 때도 그 영향이 있는가 

    - 복합 인덱스를 온전히 사용하는가 하지 못하는 가에 대해 이야기 했는데 영향을 주긴 한다. 

      이를 위해 테스트를 해보았는데 복합 인덱스를 사용 시 두번째 컬럼이 변형되면 제대로 복합 컬럼의 인덱스를 사용하지는 못한다. 

      제대로 사용은 못한다고 하더라도 복합 컬럼 인덱스를 통해 index를 사용할 수 있었으며 

      복합 컬럼만 출력 시 테이블 access도 일어나지 않았다. 

      다만 인덱스 사용시 두번째 조건이 access로 사용 되는가 filter 조건으로 달라졌다. 

      

      일반적으로 predicate에서 access만 나왔을 경우와 access와 filter가 함께 나왔을 경우에 

      access만 나온 경우가 더 효율적이라고 생각한다. 

      이유는 access는 필요한 block만 읽으면 되지만 access와 filter가 함께 나온 경우는 

      access를 통해 필요한 block을 읽고난 후 filter를 하는 작업을 또 해야 하는 것이다. 

      물론 추가적인 작업이 있기 때문에도 효율성을 이야기 하지만 

      생각해보면 인덱스에 a, b 컬럼이 속해 있는 복합 인덱스의 경우

      인덱스를 온전히 사용하여 인덱스에서 a, b 컬럼 둘 다 값을 가지고 있는 block에 access 해서 

      그 값을 가져 오기만 하면 된다. 

      그러나 b 컬럼을 사용하지 못한다면 결국 

      a 컬럼에 해당 하는 블록들을 모두 읽고 난 후 b 컬럼 값의 조건을 filtering 해야 한다. 

      이는 즉 읽어 들이는 block 수에서도 비효율이 발생할 수 있는 부분이다. 

            

      그런데 아래 쿼리로 해당 작업을 테스트 할 때는 두번째 조건이 filter 조건으로 사용 되었을 때 더 성능이 좋게 나왔다는 것이다. 

      이 부분은 현재 확실히 이해가 되지 않는다. 

      온전히 인데스를 이용하는 것보다 두번째 컬럼을 이용하지 않을 때 더 좋은 성능이 나왔기 때문이다. 

      이는 어떻게 설명할 수 있는지 추가적인 좀 더 봐야 할 부분이다. 

      

    테스트) 

    

     create table emp1 

      as select * 

         from emp 

         ;

    

     create index emp1_deptno_hiredate on emp1(deptno,hiredate) ;


     select /*+ index(emp1 emp1_deptno_hiredate) */ deptno , hiredate , count(*)

      from emp1

     where deptno = 10 

       and  to_char(hiredate,'yyyymm') = '198105' 

     group by deptno, hiredate

     order by deptno

     ;

     

     select /*+ index(emp1 emp1_deptno_hiredate)  */  deptno , hiredate , count(*)

      from emp1

     where deptno = 10 

      and  hiredate = to_date('19820123', 'yyyymmdd') 

     group by deptno, hiredate

     order by deptno

     ;

     





    

 SQLP 19회차 실습 1번 문제 

 

  문제를 참고한 블로그 주소 

   - 꿈을 펼처라 님의 블로그    


  simverse.tistory.com/85 




1) 내가 생각하는 답 


--- query 


select /*+ leading(b a) use_nl(a) index(a 고객_pk)  index(b 계약_x02) */ a.고객번호, a.고객이름, a.주민번호, b.계약일, b.계약번호, b.서비스시작일, b.서비스완료일 

  from 고객 a , 계약 b 

  where a.고객번호 = b.고객번호 

   and b.계약일 between :C and :D 

   and not exists ( select /*+ index(a 고객_x01) */ 'x' 

                     from 고객 c

                    where (a.고객이름, a.주민번호) in (:A, :B)

                   )

  union all 

select /*+ ordered use_nl(b) index(b 계약_x01) */ a.고객번호, a.고객이름, a.주민번호, b.계약일, b.계약번호, b.서비스시작일 b.서비스완료일 

  from 고객 a , 계약 b 

  where a.고객번호 = b.고객번호 

   and b.계약일 between :C and :D 

   and exists ( select /*+ index(a 고객_x01) */ 'x' 

                  from 고객 c 

                 where (a.고객이름, a.주민번호) in (:A, :B)

                   and a.rowid = c.rowid

              )

;


-- index 


고객_pk = 고객번호 

고객_x01 = 고객이름 + 주민번호 --- 가능하면 이 컬럼들에 not null 제약이 있으면 더 좋음 


계약_pk = 고객번호 + 계약번호 

계약_x01 = 고객번호 + 계약일 

계약_x02 = 계약일  



2) 이에 대한 나의 해설 


 - 이 문제를 풀어보면서 가장 고민 했던 것은 where 절에 exists 서브쿼리 작성이었다. 

   위의 쿼리를 보면 exists 일때는 where 절에 a.rowid = b.rowid라고 조건을 명시한다. 

   그러나 not exists 일때는 a.rowid = b.rowid를 넣어주지 않았다. 

   여기서 exists 일때 해당 조건을 넣어 준것은 서브쿼리 캐싱 기능을 사용하기 위해서이다. 

   조건이 exists 인 경우 해당 조건을 인덱스를 통해서 부분 범위 처리를 하게 되면 

   random access 양을 크게 줄일 수 있다. 

   그러나 이때 a.rowid = b.rowid 조건을 사용하지 않으면 서브쿼리 캐싱 기능을 사용할 수 없어서 

   인덱스가 조인 후 fiter 조건으로만 사용 될 것이다. 

   그런 비효율을 줄이기 위해서 a.rowid = b.rowid 조건을 넣어준다. 


   그런데 이때 not exists 에서는 a.rowid = b.rowid 를 넣어준 것은 비효율에 문제와 함께 잘못 된 값을 출력하기 때문이다. 

   not exists 시에는 고객이름과 주민번호가 모두 테이블에 존재할 시  'no rows selected' 가 되어야 하는데 

   a.rowid = b.rowid 조건을 추가하게 되면 조인 시 결과 값이 출력 될 것이다. 

   ( 이 부분은 설명이 끝나고 간단한 예시로 증명해볼 수 있다. )  

   그리하여 not exists 부분에는 a.rowid = b.rowid 조건을 빼버렸다. 

   그리고 추가로 고객 테이블의 where 절에 조건을 저런식을 넣어준 것은 인덱스를 사용할 수 있게 함이다.


   그리고 union all 을 기준으로 exists 부분 쿼리와 not exists 부분 쿼리에 힌트가 달라졌는데 이는 driving table 순서가 바뀜을 의미한다. 

   exists 의 경우 이름과 주민번호의 조건으로 소수의 값만 출력 될 것으로 생각 된다. 

   그렇게 되면 nl 조인 시 계약 테이블에 조인 되는 양을 줄일 수 있다. 

   그러나 해당 조건이 없다면 고객 테이블에서는 부분 범위 처리를 할 수 있는 값이 없다. 

   그래서 해당 순서와 같이 한다면 100만 건을 조인 시도 할 것이다. 

   그래서 not exists 부분에서는 조인 순서를 바꿔주었다. 

   7일 이기 때문에 최대 7000 건이다. 100만 건 보다는 훨씬 줄일 수 있을 것으로 생각하였다. 


   또한 index 부분을 보면 '고객번호 + 계약일' 인덱스가 있음에도 불구하고 따로 '계약일' 인덱스를 따로 생성하여 사용하였다. 

   이 쿼리에서는 계약일을 between 범위 처리를 해야 한다. 

   해당 쿼리에 내용상 in 스타일로 변경할 수 없다. 

   그렇다고 skip scan을 써도 제대로 효과가 없을 것으로 생각했는데 그 이유는 고객번호의 변별력이 좋기 때문에디 고객번호는 pk 이기 때문이다. 

   고객번호는 변별력이 매우 큰 컬럼임이다. 

   이처럼 선두 컬럼이 변별력이 클때는 skip scan은 크 효과를 거둘 수 없다. 

   그래서 계약일 인덱스를 생성하였다. 고객이름과 주민번호가 입력되지 않을 시에는 범위 처리가 가능하게 하는 컬럼은 계약일 밖에 없기 때문이다. 

   그래서 인덱스가 2개 생성됨에도 불구하고 자주 사용되는 쿼리라면 '계약일' 인덱스가 필요하다고 생각하였다. 



3) not exists 에 대한 내용에 대한 참고 쿼리 

    - 해당 emp 테이블은 오라클 샘플 emp table 이다.   



select * 

 from emp a 

 where not exists ( select 'x' 

                      from emp b

                     where ename = 'KING'

                      and a.rowid = b.rowid 

                   )

 ;


--- 14 rows 를 출력 


select * 

 from emp a 

 where not exists ( select 'x' 

                      from emp b

                     where ename = 'KING' 

                   )

 ;

  

--- no rows selected 

   

 * 하지만 이부분에서 a.rowid = b.rowid 조건을 넣었을 때 14 rows 가 출력되는 이유는 잘 모르겠다. 

   ename = 'KING'  값이 있는 ' no rows selected '가 되어야 한다고 생각하는데 말이다.   




  





+ Recent posts