3) IOT (index organizaion table) 테이블 구성
   - table random 엑세스가 발생하지 않도록 테이블을 아예 인덱스 구조로 생성한 object 

    ⑴ IOT 의 장점 
       - 인위적으로 클러스터링 팩터를 좋게 만드는 방법중에 하나여서 Logical read를 줄일 수 있다. 
       - 기존에는 인덱스 + 테이블이였다면, lOT는 인덱스만 만들면 되므로, 저장 공간이 절약된다. 

    ⑵ IOT의 단점 
        - 데이터 입력할 때, 성능이 느리다. 
   
    ⑶ IOT 생성 및 데스트 
        ① create table emp_iot
           (empno, ename, job, mgr, hiredate, sal, comm, deptno, constraint pk_emp_iot primary key(empno) )
           organization index
            as
                 select * from emp  ;

              * IOT 생성시 pirmary key 가 없으면 테이블이 생성되지 않는다. 
              * 기존 테이블에 primary key가 걸려 있어도 의미가 없다. 새로 생기는 table에 primary key를 만들어 주어야 한다.
                  (ctas 사용시 not null 제약을 제외한 나머지 제약은 따라오지 않는다.) 

        ② select * from emp_iot ;
             -- consistent gets 5 
             -- index fast full scan
 
     ⑷ IOT 저장 영역 
         ① PK 속성 영역 : 자주 조회 조건에서 검색되는 컬럼들이 저장된 저장 영역

         ② 오버 플로우 영역 : 값은 필요해서 저장해두지만 출력이나 조회조건으로 거의 사용되지 않는 컬럼들을 저정하는 저장 영역
         
         * 영역을 나누어서 사용하는 이유는 검색 속도를 좀 더 높이기 위해서 
         * IOT는 table이 아니라 index로 저장 된다. 검색시에는 object_type 이나 segment_type을 index로 하여야 찾을 수 있다. 
         * 그러나 drop 이나 truncate 명령어를 사용할 때에는 table 옵션을 사용한다. 
 

  4) 클러스터 테이블 

     ⑴ 클러스터 테이블이란 
        - 클러스터 키 값이 같은 레코드가 한 블럭에 모이도록 저장하는 구조의 테이블을 말한다. 
        - 클러스터 테이블을 이용하면, 여러테이블의 레코드를 심지어 하나의 물리적 공간에 같이 저장할 수 있다 
        - 여러 테이블들을 서로 조인된 상태로 저장해 두는 것이다. 
       
         * 단점은 DML 속도가 상당히 느려진다. 

     ⑵ 클러스터 테이블의 종류 2가지 
         ① 인덱스 클러스터 테이블 
               - 단일 테이블 인덱스 클러스터 
               - 다중 테이블 인덱스 클러스터

         ② 해쉬 클러스터 테이블 

     ⑶ 단일 클러스터 
         - 클러스터 인덱스는 컬럼 값을 하나만 저장하기 때문에 인덱스의 크기가 크지 않아 검색속도가 높아진다.
         - 클러스터 테이블은 클러스터 키값에 따라서 최대한 같은 블럭안에 같은 키값들을 저장히기 때문에 검색속도가 높아진다. 

       
     ⑷ 단일 클러스터 테이블 생성 테스트 
     
            ① 클러스터를 생성한다. 
     
                 CREATE CLUSTER objs_cluster# (object_type VARCHAR2(20) ) INDEX ;
                                                  ㅣ                           ㅣ
                                           클러스터 이름             클러스터 key 값
                                             
            ② 클러스터 인덱스를 생성한다.
               CREATE INDEX objs_cluster_idx ON CLUSTER objs_cluster# ;
                                               ㅣ                                   ㅣ 
                                       클러스터 인덱스 이름            클러스터 이름

            ③ 클러스터 테이블을 생성한다.
                CREATE TABLE objs_cluster
                  CLUSTER objs_cluster# (object_type)
                            AS SELECT *
                                  FROM all_objects
                                ORDER BY DBMS_RANDOM.VALUE;

               * dbms_random.value를 사용해서 정렬해서 데이터를 저장하면 
                   실제로 논리적으로는 object_type 별로 저장되어있지 않고 데이터가 무분별하게 흩어져서 저장되어져있는 것처럼 보이겠지만
                   실제로 물리적으로는 object_type 별로 같은 블럭안에 데이터가 저장된다.                    

                * 생성시에는 cluster, table 모두 create 하지만 truncate 시에는 cluste만 truncate 하면 자동으로 table의 내용이 삭제 된다. 
                   drop시에는 table, cluster 모두 삭제 해야 한다.

                * 클러스터 테이블을 생성하기 위해서는 클러스터 생성시 클러스터 키값은 기존 테이블의 컬럼의 데이터 타입과 길이의 범위가 같아야 한다. 
                   그렇지 않을 시 클러스터 테이블 생성시 error가 발생한다. 
 
            ④ 일반 테이블과 클러스터 테이블의 성능을 비교한다. 
 
               CREATE TABLE objs_regular
                      AS SELECT *
                             FROM objs_cluster
                          ORDER BY DBMS_RANDOM.VALUE;

               CREATE INDEX objs_regular_idx ON objs_regular(object_type) ;

               ALTER TABLE objs_regular MODIFY object_name NULL;
               ALTER TABLE objs_cluster MODIFY object_name NULL;
  
       결과 )  -- consistent gets , physical read  순 

                   SELECT *
                     FROM objs_regular
                   WHERE object_type='TABLE';
                     -- 429
                     -- 235
                     -- full table access

                    SELECT /*+ index(objs_regular objs_regular_idx) */*
                     FROM objs_regular
                   WHERE object_type='TABLE';
                      -- 549
                      -- 235 
                      -- index range scan 

                  SELECT *
                    FROM objs_cluster
                 WHERE object_type ='TABLE';
                     -- 226
                     -- 199
                     -- index unique scan 
                     -- table access cluster 

                 
    5) 다중 클러스터 테이블 
         - 여러 테이블의 레코드를 물리적으로 같은 블럭안에 저장되게 하는 테이블을 말한다. 
         - 즉, 여러테이블들을 서로 조인된 상태로 저장해두는 것을 말한다.      
        

      ⑴ 다중 클러스터 테이블 구현 
          ① 클러스터를 생성한다. 
              create cluster c_deptno# on ( deptno number(20) ) index  ;
           
          ② 클러스터 인덱스를 생성한다. 
             create index i_deptno# on cluster c_deptno# ;

          ③ 클러스터 테이블을 생성한다. 
              create table emp_cluster
                cluster c_deptno#(deptno)   -----------------------> 같은 클러스터 사용 
                 as select * 
                       from emp ;

           
             create table dept_cluster
                cluster c_deptno#(deptno)   -----------------------> 같은 클러스터 사용 
                 as select * 
                       from dept ;

              * 두개의 테이블이 같은 블럭안에 존재한다는 것을 확인해보시오 ! 

                  break on deptno skip 1 ;

                  select d.deptno, e.empno, e.ename,
                            dbms_rowid.rowid_block_number(d.rowid) deptno_block_no,
                            dbms_rowid.rowid_block_number(e.rowid) emp_block_no     
                   from dept_cluster d , emp_cluster e
                   where e.deptno = d.deptno
                   order by d.deptno ;
    

          ④ 일반 테이블과 클러스터 테이블과 성능을 비교한다. 

             select e.ename, d.loc, e.sal, e.hiredate, d.dname, e.deptno, e.mgr
                 from emp e, dept d
                where e.deptno = d.deptno and d.loc ='DALLAS';
               
                  * 순서대로 1회 실행과 2회 실행 시 

                  -- 11  -> 7
                  -- 4   -> 0
                  -- table acess full
                  -- hash join

            select e.ename, d.loc, e.sal, e.hiredate, d.dname, e.deptno, e.mgr
                 from emp_cluster e, dept_cluster d
                where e.deptno = d.deptno and d.loc ='DALLAS';

                -- 26 -> 10
                -- 6  -> 0
                -- table access cluster
                -- table acess full
                -- nested loop


문제 34. 사원 번호가 7788 번인 사원의 모든 컬럼의 데이터를 가져오는 쿼리의 성능을 emp 와 emp_iot 로 비교하시오 

 SELECT /*+ full(emp) */ *
 from emp
 WHERE empno=7788 ;
 -- consistent gets 4 
 -- full table scan 


 select *
 from emp_iot
 WHERE empno=7788 ;
 -- consistent gets 1 
 -- index unique scan 


문제 35. rn 이라는 컬럼이 있는 sales500 테이블을 rn 컬럼을 primary key 로 해서 iot를 생성하는데  이름을 sales500_iot로 구현하시오 ! 

1) rn 컬럼 추가 

CREATE TABLE SALES500
  AS SELECT ROWNUM rn, b.*
       FROM SALES b ;

2) IOT 구성 

CREATE TABLE sale500_iot
  (RN ,PROD_ID, CUST_ID, TIME_ID, CHANNEL_ID, PROMO_ID, QUANTITY_SOLD, AMOUNT_SOLD,
   CONSTRAINT sale500_iot_pk PRIMARY KEY(rn) )
   organization index
            as
             select * from sales500  ;


문제 36. 아래의 쿼리의 검색 속도를 비교하시오! 

 select *
 from sales500
where rn = 384756 ;
-- consistent gets 5076
-- full table scan 

select *
 from sale500_iot
where rn = 384756 ;
-- consistent gets 3
-- index unique scan 

* rn 컬럼에 primary key가 걸려 있기 때문에 자동으로 unique index가 생성되었다. 


문제 37. sales500 table + sales500_rn index 와 sales500_iot table 의 크기 차이를 비교하시오 ! 

SELECT segment_name, segment_type, bytes/1024/1024 MB
 FROM user_segments
 WHERE segment_name LIKE 'SALE%' ;

- sales500 (table) + sales500_rn_pk(index) = 41 + 16 = 57
- sales500_iot_pk = 39 


문제 38. SALES500 테이블과 SALES500_IOT 테이블에 insert 할 때의 속도 차이를 비교해보시오! 
- 행의 갯수 918843

첫 실험 
- sales500  : 7 초
- sales500_IOT :31 초

* 사람들 마다 차이가 있어서 여러번 실험해보아야 한다. 
* 여러번 실험할 수록 차이는 줄어 든다. 그 이유는 insert 시에 high water mark를 높여 놨기 때문이다.
* 제대로 실험해보기 위해서는 해당 table을 drop 한 후 다시 생성하고 buffer cache를 지우고 실험해보아야 한다.  


문제 39. sales500  테이블을 클러스터 테이블로 구성하는데 클러스터 키컬럼을 amount_sold로 해서 생성하시오 ! 

 CREATE CLUSTER sales500_cluster# (amount_sold VARCHAR2(22) ) INDEX ;

 CREATE INDEX sales500_cluster_idx ON CLUSTER sales500_cluster# ;

  CREATE TABLE sales500_cluster
     CLUSTER sales500_cluster# (amount_sold)
           AS SELECT *
                FROM sales500
              ORDER BY DBMS_RANDOM.VALUE;

  CREATE TABLE sales500_regular
          AS SELECT *
        FROM sales500_cluster
       ORDER BY DBMS_RANDOM.VALUE;

  CREATE INDEX sales500_regular_idx ON sales500_regular(amount_sold) ;


문제 40. sales500 테이블과 sales500_cluster 테이블과 성능을 비교하시오 

* 각각 실험 마다 flush buffer_cache 실행 
* 1차, 2차 실행 

select /*+ full(sales500_regular) */*
 from sales500_regular
where amount_sold=136.64 ;
-- 5079 -> 5079
-- 5070 -> 5069


select /*+ index(sales500 sales500_regular_idx) */ *
 from sales500_regular
where amount_sold=136.64 ;
-- 94 -> 90
-- 88 -> 0

select *
 from sales500_cluster
 where amount_sold=136.64 ;
-- 9   -> 9
-- 3   -> 0


문제 41. 아래의 조인문장의 성능이 좋아지도록 SALES 테이블과 PRODUCTS 테이블을 다중 클러스터 테이블과 구성하시오 ! 

create table sales                                    create table products
 as                                                             as 
     select * from sh.sales ;                           select * from sh.products ;

select  /*+ leading(p s) use_nl(p) */ p.prod_name, sum(s.amount_sold) 
   from sales s, products p 
 where s.prod_id = p.prod_id 
    and p.prod_id = 40
  group by p.prod_name ;

-- 4597  ->  4441
-- 4576   -> 4433
-- table acess full 
-- table acess full 
-- hash join 

 SELECT p.prod_name, sum(s.amount_sold)
   from sales_cluster s, product_cluster p
 where s.prod_id = p.prod_id
    and p.prod_id = 40
  group by p.prod_name ;
  -- 1238 -> 244
  -- 619  -> 0
  -- TABLE ACCESS CLUSTER -> INDEX UNIQUE SCAN
  -- TABLE ACCESS CLUSTER -> INDEX UNIQUE SCAN
  -- NESTED LOOPS


* 점심시간 문제 :

  create table emp_iot2
  (empno, ename, job, mgr, hiredate, sal, comm, deptno,
   CONSTRAINT emp_iot2_pk PRIMARY KEY (empno,ename) )
    organization INDEX
    INCLUDING sal
    OVERFLOW tablespace ts01
     as
    select * from emp  ;

-- 조회를 * 로 함 
-- where 조건 including 밖

   SELECT *
     FROM emp_iot
     WHERE deptno = 10;
   -- 5

   SELECT *
    FROM emp_iot2
     WHERE deptno = 10 ;
   -- 7 

-- 조회를 * 로 함 
-- where 조건 including 안

    SELECT *
     FROM emp_iot
     WHERE job = 'SALESMAN';
   -- 5

   SELECT *
    FROM emp_iot2
     WHERE job = 'SALESMAN';
    -- 7

-- 조회를 * 로 함 
-- where 조건 PK 컬럼
 
    SELECT *
     FROM emp_iot
     WHERE empno = 7788;
     -- 1

   SELECT *
     FROM emp_iot2
     WHERE empno = 7788;
     -- 3

* 아래부터는 할 때마다 alter system flush buffer_cache ; (buffer cache 내용 지우기) 를 실행함 
* 그래서 consistent get, physical read 둘다 기록 

-- where 조건 pk 컬럼
-- including 전 컬럼까지 조회

    SELECT empno, ename, job
     FROM emp_iot
     WHERE empno = 7788;
     -- 1
     -- 8

    SELECT empno, ename, job
     FROM emp_iot2
     WHERE empno = 7788 ;
    -- 7
    -- 6

-- where 조건 pk 컬럼 x
-- including 전 컬럼까지 조회

   SELECT empno, ename, job
     FROM emp_iot
     WHERE deptno = 10;
   -- 10
   -- 6

   SELECT empno, ename, job
    FROM emp_iot2
     WHERE deptno = 10 ;
   -- 13
   -- 14

-- where 조건 pk 컬럼
-- including 이외의 컬럼까지 조회

   SELECT empno, ename, deptno
     FROM emp_iot
     WHERE empno = 7788;
   -- 1
   -- 8 

   SELECT empno, ename, deptno
    FROM emp_iot2
     WHERE empno = 7788 ;
   -- 3
   -- 16   


-- where 조건 pk 컬럼 x
-- including 이외의 컬럼까지 조회

   SELECT empno, ename, deptno
     FROM emp_iot
     WHERE deptno = 10;
   -- 10
   -- 6

   SELECT empno, ename, deptno
    FROM emp_iot2
     WHERE deptno = 10 ;
   -- 13
   -- 14


* 오늘의 마지막 문제 

SALES500_IOT 와 SALES_CLUSTER 와 SALES 테이블 3개의 DML 속도를 비교하시오 !

* 모두 truncate 로 아무 것도 없던 상태에서 작업 
* db도 테스트하기 전에 startup force 실행 
* 각 테스트가 끝나면 flush buffer_cache 후 다시 테스트 진행함 

INSERT INTO SALEs500
  SELECT *
  FROM sales500_backup ;
-- 5초 13

INSERT INTO sales500_iot
  SELECT *
  FROM sales500_backup ;
-- 26초 71

INSERT INTO sales500_cluster
  SELECT *
  FROM sales500_backup ;
-- 45초 59 

* sales500 에도 sales500_iot와 동일한 조건을 주기 위해서 rn 컬럼에 pk 제약을 건 후 다시 테스트 
* 이번에는 좀 더 정확한 결과를 위해서 각 테스트 마다 startup 을 새로함 

INSERT INTO SALEs500
  SELECT *
  FROM sales500_backup ;
-- 5초 07

INSERT INTO sales500_iot
  SELECT *
  FROM sales500_backup ;
-- 22초 71

INSERT INTO sales500_cluster
  SELECT *
  FROM sales500_backup ;
-- 40초 49 

- 조금 씩 위의 결과보다 차이는 나지만 큰 차이는 발생하지 않았다. 




  7) index merge scan 
        - 두개의 인덱스를 동시에 사용해서 하나의 인덱스만 사용했을 때보다 더 큰 시너지 효과를 보겠금 하는 스캔 방법 
           (시너지 효과 : table 엑세스 횟수를 줄이는 효과가 있다.)
            (10g 부터는 사용되지 않고 있는 기능이다. )  
        -  힌트 : and_equal 

     ⑴ index merge test
        ① @ctas (초기화)
        ② create index emp_deptno on emp(depno) ; 
        ③ create index emp_job on emp(job) ;

     ⑵ index merge 실행계획 

     SELECT /*+ and_equal(emp emp_deptno emp_job) */ ename, job, deptno
      FROM EMP
     WHERE deptno = 30
       AND job = 'SALESMAN';    
 
------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |     1 |    14 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| EMP        |     1 |    14 |     3   (0)| 00:00:01 |
|   2 |   AND-EQUAL                 |            |       |       |            |          |
|*  3 |    INDEX RANGE SCAN         | EMP_JOB    |     3 |       |     1   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN         | EMP_DEPTNO |     5 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

   1 - filter("JOB"='SALESMAN' AND "DEPTNO"=30)
   3 - access("JOB"='SALESMAN')
   4 - access("DEPTNO"=30)
 
* 실행계획을 읽을 때 동일한 위치에 있는 것들은 위에서 부터 읽으면 된다. 

 8) index bitmap merge scan  ( 10g 버젼부터 가능한 스캔 방법 _ 책에는 9c 부터)
     - index merge scan 과 스캔방법은 똑같은데 인덱스의 크기를 줄이기 위해서 인덱스를 bitmap 으로 변환하는 작업이 추가되었다. 
     - hint : index_combine  

    SELECT /*+ index_combine(emp) */ ename, job, deptno
    FROM EMP
    WHERE deptno = 30
     AND job = 'SALESMAN';
      -- 4 
   
-----------------------------------------------------------------------------------------------
| Id  | Operation                        | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |            |     1 |    14 |     3   (0)| 00:00:01 |        -----------------------------------------> 실행순서 8
|   1 |  TABLE ACCESS BY INDEX ROWID     | EMP        |     1 |    14 |     3   (0)| 00:00:01 |  ------------------------------------------> 실행순서 7
|   2 |   BITMAP CONVERSION TO ROWIDS    |            |       |       |            |          |     ------------------------------------------> 실행순서 6
|   3 |    BITMAP AND                    |            |       |       |            |          |             ------------------------------------------> 실행순서 5
|   4 |     BITMAP CONVERSION FROM ROWIDS|            |       |       |            |          |   ------------------------------------------> 실행순서 2
|*  5 |      INDEX RANGE SCAN            | EMP_JOB    |       |       |     1   (0)| 00:00:01 |     ------------------------------------------> 실행순서 1
|   6 |     BITMAP CONVERSION FROM ROWIDS|            |       |       |            |          |   ------------------------------------------> 실행순서 4
|*  7 |      INDEX RANGE SCAN            | EMP_DEPTNO |       |       |     1   (0)| 00:00:01 |   ------------------------------------------> 실행순서 3
-----------------------------------------------------------------------------------------------

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

   5 - access("JOB"='SALESMAN')
   7 - access("DEPTNO"=30)

※ 선택도 확인 방법 
      ① ANALYZE TABLE EMP COMPUTE STATISTICS ;

      ② select c.column_name, t.num_rows, c.num_nulls, c.num_distinct
                    , 1/c.num_distinct selectivity
                    , num_rows/c.num_distinct cardinality
           from   user_tables t, user_tab_columns c
         where  t.table_name = 'EMP'
            and   c.table_name = t.table_name
            and   c.column_name in ('DEPTNO','JOB') ;   


- num_distinct 가 클 수록 선택도가 좋아진다. (num_distinct : 해당 컬럼을 구분하는 구분기준의 갯수)
  (selectivity, cardinality 의 값이 작을 수록 선택도가 좋아진다.) 
- 선택도가 좋아진다는 것은 optimize 가 선택할 가능성이 크다는 것을 의미한다. 

 9) index join
   - 인덱스끼리 조인해서 바로 결과를 보고 테이블 엑세스는 따로 하지 않는 스캔 방식 
   - 힌트 : index_join

   ⑴ index join 테스트
      ① create index emp_deptno on emp(deptno) ;
         create index emp_job on emp(job) ;

      ② select deptno, job
          from emp
          where deptno = 30 and job='SALESMAN'; 
        -- consistent gets 4 
        -- full table scan  

      ③ SELECT /*+ index_join(emp emp_deptno emp_job) */ deptno, job
           FROM EMP
          where deptno = 30 and job='SALESMAN';
         -- consistent gets 3
         -- 2번의 index range scan 
         -- hash join 

 10) index unique scan 
       - primary key 나 unique 제약을 걸면 unique 인덱스가 자동으로 생성이 되는데 바로 이 unique 인덱스를 이용해서 데이터를 스캔하는 방법 
         ( 생성할 수만 있다면 non unique 인덱스보다 성능이 좋다.) 
       - 힌트 : index ( 해당 컬럼에 unique 제약이 있으면 자동으로 적용된다.) 

        ⑴ index unique scan 생성 방법  
             ①  primary key 생성시 unique 인덱스 자동 생성)
               alter table emp 
               add constraint emp_empno_pk primary key(empno) ;

               select index_name, uniqueness
                 from user_indexes
               where table_name ='EMP'
               
           
             ②  unique index 직접 생성
               - create unique index emp_comm on emp(comm);
             
         ⑵ unique index 의 특징               
             - non unique index는 unique 하다고 선언되어 있지 않아서 필요한 자료를 찾은 후 또 자료 있는지 그 행 다음열 부터 다시 찾는 작업을 한 번더 실행한다.)
             - 즉, unique index는 필요한 data를 찾는 1번의 검색만 하는 반면 non unique index는 총 2번의 검색 작업이 이루어진다.)


(4) 테이블 random 엑세스 부하 
     1) 테이블 랜덤 엑세스 줄이는 방법 
         ① 결합 컬럼 인덱스를 활용한다. 
         ② index의 클러스터링 팩터를 좋게한다. (p. 68)
         ③ index의 컬럼추가 (p. 79)               
         ④ 테이블의 구조를 IOT (index organizaion table) 로 구성한다. (p. 98)
         ⑤ 테이블의 구조를 cluster table로 구성한다. (p.118)
 
     2) index 컬럼추가 
         - 기존에 사용되던 index에서 필요한 컬럼을 추가하여 table access를 줄인다

          ①   create table ts
                 as select *
                  from all_objects
                  order by object_type ;

          ② create index t2_owner
              on t2(owner) ;

          ③ alter system flush buffer_cache ; ----------------> buffer cache 내용 비우는 작업 

    ※ 차세대 시스템 구축시 주의 사항 !! 
 
         <예시>
            SQL> insert /*+ parallel(e1,4) */ into emp e1
            SQL> select /*+ parallel(e2,4) */ *
                     from emp e2; 

      - 데이터 이행시 위와 같이 병렬로 작업해서 이행하면 데이터가 무작위로 흩어져서 저장되기 때문에 클러스터링 팩터가 더 안좋아져서 성능이 느려질 수 있다. 


문제 29. 부서번호가 30번이고 직업이 SALESMAN인 사원들의 이름과 직업과 부서번호를 출력하시오! 
          (실행계획을 보고 인덱스 둘중에 어느 인덱스를 사용했는지 확인하시오!) 

SELECT ename, job, deptno
 FROM EMP
 WHERE deptno = 30
  AND job = 'SALESMAN';       

- emp(job) 에 걸려 있는 index를 사용 
- emp(job) 으로는 4건 , emp(deptno) = 6 건 
- 더 적게 table acess 하는 방법으로 optimize 가 선택하였다.

* 튜닝을 할 때는 where 조건에 걸려 있는 각각의 건수들이 몇건인지 확인해보는 것이 기본이다. 
* 옵티마이져가 인덱스의 선택도를 조사해서 선택도가 좋은 인덱스를 선택했다. 
     ( emp_job > emp_deptno) 


문제 30. 위의 SQL 의 실행계획이 부서번호의 인덱스를 엑세스 하겠금 힌트를 주시오 ! 

SELECT /*+ index(emp emp_deptno) */ ename, job, deptno
 FROM EMP
 WHERE deptno = 30
  AND job = 'SALESMAN'; 


문제 31. 아래의 SQL 의 컬럼에 각각 인덱스를 걸고 두개의 실행계획을 비교하는데 하나는 index merge scan 이 되게하고 , 
          다른 하나는 index bitmap merge scan 이 되게해서 consistent gets를 비교하시오 ! 

튜닝 전 :

select * 
 from sales500
where channel_id = 3 
  and amount_sold= 1232.16 ;

 --  consistent gets 4442
 -- (full hint 사용 시) 

튜닝 후:

1) 일반적인 인덱스 사용 

select /*+ index(sales500 sales500_amount) */ *
 from sales500
where channel_id = 3
  and amount_sold= 1232.16 ;

-- consistent gets 18
-- index range scan 

2) index merge scan 사용

SELECT /*+ and_equal(sales500 sales500_channel sales500_amount) */ *
 from sales500
where channel_id = 3
  and amount_sold= 1232.16 ;

-- consistent gets 31
-- index range scan 2번

3) index bitmap merge scan 

SELECT /*+ index_combine(sales500) */ *
 from sales500
where channel_id = 3
  and amount_sold= 1232.16 ;     

-- consistent gets 412
-- index range scan
-- bitmap conversion 


문제 32. 아래의 SQL 을 튜닝하시오 ! 

튜닝 전:

select * 
 from t2
 where owner='SCOTT' and object_name='EMP';

-- consistent gets 232

튜닝 후:

1) index column 1개만 사용 

 select /*+ index(t2 t2_owner)*/ *
 from t2
 where owner='SCOTT' and object_name='EMP';

  -- consistent gets 30
  -- index range scan

 2) index column 을 2개 사용하여 복합 컬럼 인덱스 구성  

 select /*+ index(t2 t2_owner_name)*/ *
 from t2
 where owner='SCOTT' and object_name='EMP';
  -- consistent gets 4
  -- index range scan 


문제 33. 짝궁의 SCOTT 의 SALES500 테이블의 SALES500_CUST_ID 인덱스의 클러스터링 팩터를 비교하시오! 

SQL> select a.index_name, a.clustering_factor, b.index_name, b.clustering_factor
          from user_indexes a, user_indexes@shs_link2 b
      WHERE a.table_name = 'SALES500';


* 점심시간 문제 : 아래의 SQL 을 튜닝하시오 ! 
                    (deptno , sal 2개의 컬럼으로 구성 된 index가 있을 시 )   

create index emp_deptno_sal on emp(deptno,sal);


튜닝 전 :

select deptno, max(sal)
  from emp
where deptno = 20
 group by deptno ; 

-- 46
-- full (full 힌트를 주어서 작동 시켰을 때 값)  


튜닝 후 : 
1) 일반적인 복합 컬럼 인덱스를 사용하여 튜닝

SELECT /*+ index_desc(emp emp_deptno_sal) */ deptno, sal
  FROM EMP
 WHERE deptno = 20
  AND ROWNUM = 1;

-- consistent gets 1
-- index range scan 

2) 인라인 뷰 안에서 먼저 복합 컬럼 인덱스를 사용하여 튜닝 후 첫번째 로우만 뽑아 내기 
 
 SELECT *
 FROM (SELECT /*+ index_desc(emp emp_deptno_sal) */ deptno, sal
         FROM EMP
        WHERE deptno = 20 )
 where rownum = 1 ;

-- consistent gets 1
-- index range scan 

3) 인라인 뷰 안에서 스킵 스캔을 사용하여 튜닝 후 첫번째 로우만 뽑아 내기 

SELECT *
 FROM (SELECT /*+ index_ss_desc(emp emp_deptno_sal) */ deptno, sal
         FROM EMP
        WHERE deptno = 20 )
 where rownum = 1 ;

-- consistent gets 1
-- index skip scan 


  3) full table scan 
       - High water mark 까지 스캔하는 방법 
          (HWM - 포멧된 디스크와 포멧되지 안은 디스크의 경계선)
       - full table scan 방법은 인덱스 스캔은 아니지만 인덱스가 없는 경우 발생하는 기본 스캔임으로 알아둘 필요가 있다.  

     ⑴ Full table scan을 할 수 밖에 없는 경우 
       ① 인덱스가 없을 때 
       ② full 힌트를 사용하면 
       ③ 인덱스를 생성할 때 
       ④ 테이블의 통계정보를 수집할 때 

     ⑵ Full table scan을 할 수밖에 없다면 full table scan 속도를 높이는 방법 
        ① full table scan 에 관련한 오라클 파라미터를 조정 
            - 파라미터 : db_file_multiblock_read_count  
              ( 이 파라미터는 full table scan 을 할 때 한번에 읽어들이는 블럭의 갯수를 결정하는 파라미터 ) 

            alter session set db_file_multiblock_read_count = 128;

       ② 병렬로 작업한다.
            select /*+ full(emp) parallel(emp,4) */ ename, sal, job
             from emp 
           where job = 'SALESMAN';

    ⑶ full table scan 속도 향상 테스트

     ① set timing on
     ② create table sales800
           as 
               select * from sh.sales ;

     ③ alter session set db_file_multiblock_read_count=4 ;
        alter session set workarea_size_policy=manual ;
        alter session set sort_area_size=10 ;      

     ④ create index sales_index
           on sales800(amount_sold, prod_id) ;
          -- 시간 확인 
     ⑤ drop table sales800 purge ;
     ⑥ 다시 scott 으로 접속해서 
     ⑦ 2번부터 다시 수행하는데 아래와 같이 128로 설정하고 테스트 
          alter session set db_file_multiblock_read_count=128 ;
          alter session set workarea_size_policy=manual ;
          alter session set sort_area_size=10000000 ;  

 4)  index range scan 
     - 인덱스의 일부분만 범위 스캔해서 DATA를 엑세스 스캔 방법 
        힌트 :  /*+ index (emp emp_sal) */ 
              (기본적으로 사용하는 hint 는 index range scan을 이용하라는 의미이다.) 

    ⑴ 실습 환경 구성 
       
      drop table sales500 purge
       
      create table sales500
       as 
         select rownum rn , s.* 
            from sh.sales s;

       create index sales500_rn on sales500(rn) ;
       create index sales500_promo_id on sales500(promo_id);

      튜닝 전 : select /*+ full(sales500) */ * 
                      from sales500
                      where rn=299;

       튜닝 후 : select /*+ index(sales500 sales500_rn) */ *
                      from sales500
                      where rn=299; 

    * 실행 통계 및 자동 추적 사용 시 (SQL gate ) 

     db block gets       ┐
     consistent gets     ┘  ------------> 메모리 영역 
     physical reads    ------------------> 디스크 영역


  5) index full scan 

     ⑴ index full scan 의 종류 
       - index full scan  : 인덱스 full 로 스캔 
           (힌트 : index_fs )                   

       - index fast full scan  : 인데스 full 로 스캔하는데  index full scan 보다 더 속도가 빠르다.  
           (힌트 : index_ffs )   (multiblock i/o를 하기 때문에)
           

     ⑵ index full scan 과  index fast full scan 의 차이 
         index full scan                    vs          index fast full scan 
     -  인덱스 구조에 따라 스캔                   세그먼트 전체를 스캔 
     -  순서가 보장 (정렬 됨)                     순서가 보장이 안된다. (정렬이 보장이 안된다는 것이지 아예 안된다는 것은 아니다.)
     -  single block i/o                            multi block i/o
     -  병렬 스캔이 블가능하다.                   병렬스캔이 가능하다. 


      * SELECT /*+ index_fs(sales500 sales500_rn) parallel_index(sales500,sales500_rn,4) */ count(rn)
             from sales500;
           ( 위의 SQL 은 index full scan으로 hint 를 사용하여 병렬처리는 실행계획에 포함되지 않는다.) 

  6) index skip scan  스캔 방법 
     - 인덱스를 full  또는 fast full 로 전체를 스캔하는 것이 아니라 중간 중간 skip 을 해서 사용
        (힌트 : index_ss)  

      ⑴ index skip scan 이란 
         - 인덱스의 첫번째 컬럼이 where 조건이 없어도 인덱스를 사용할 수 있게 한다.
            ( 이전에는 결합 컬럼 인덱스의 첫번째 컬럼이 where 조건에 있어야만 결합 컬럼 인덱스를 엑세스 할 수 있다. ) 
          
             예시 ) index의 컬럼 순서에 따라서 index를 사용하지 못하는 경우
     
                       create index emp_deptno_sal 
                           on emp(deptno, sal) ;

                       select ename, sal 
                         from emp 
                       where sal = 3000;     ----------------> 위의 index 사용 못 함
 
                       select ename, sal 
                         from emp 
                        where deptno = 10; -----------------> 위의 index를 사용 함 
                       
                        ( 실무에서는 인덱스를 임의로 추가하기 힘들다.)

                      SELECT /*+ index_ss(emp emp_deptno_sal) */ ename, sal
                          from emp
                        where sal = 3000;


      ⑵ index skip scan 스캔 방법 설명 

        SELECT /*+ index_ss(emp emp_deptno_sal) */ ename, sal
            from emp
          where sal = 2975;

          DEPTNO    SAL    ROWID

                 10    1300    AAAg7WAAZAAAAfrAAQ
                 10    2975    AAAg7WAAZAAAAfrAAF
                 20     800    AAAg7WAAZAAAAfrAAN
                 20    1100    AAAg7WAAZAAAAfrAAP
                 20    2975    AAAg7WAAZAAAAfrAAG
                 20    3000    AAAg7WAAZAAAAfrAAM
                 20    3000    AAAg7WAAZAAAAfrAAO
                 30     950    AAAg7WAAZAAAAfrAAK
                 30    1250    AAAg7WAAZAAAAfrAAH
                 30    2975    AAAg7WAAZAAAAfrAAL
                 30    3500    AAAg7WAAZAAAAfrAAJ
                 30    3600    AAAg7WAAZAAAAfrAAI
                 30    4050    AAAg7WAAZAAAAfrAAE

            - deptno 번호별 해당 sal 의 data 가 있으면 값을 찾고 다름 deptno 그룹으로 넘어간다. 
            - 해당 경우에서는 deptno의 종류가 적을 수록 효과적이다. 
               ( 앞의 컬럼이 나누어지는 경우의 수가 적을 수록 더 빠르게 값을 찾을 수 있다.) 
    
        * 결합 컬럼 인덱스의 첫번째 컬럼이 where 절에서 선분조건 (betwwen .. and like) 으로 사용 된다면 
             인덱스를 넓게 읽을 수 밖에 없기 때문에 성능이 저하된다. 그럴때는 두가지 방법으로 튜닝한다. 
                 1 index skip scan 사용 
                 2 between .. and 를 in으로 변경 

            (그림은 index 사용 시에 대한 실행 내용)           
           



문제 17. 아래 SQL 의 성능을 높일 수 있도록 인덱스를 생성하시오 ! 

튜닝 전 :
             select * 
               from sales500 
            where cust_id=35834 ;

튜닝 후 :
           CREATE INDEX sale500_cust ON SALES500(cust_id);

              select /*+ index (sales500 sales500_cust) */ * 
               from sales500
             where cust_id=35834 ;


문제18. EMP테이블에 사원 수가 몇명인지 조회하시오.

튜닝 전 :
          select count(*) 
            from emp ;

튜닝 후 : 
          select /*+ index (emp emp_sal) */ sal 
            from emp ;


문제 19. 아래의 SQL 의 성능을 높일 수 있도록 인덱스를 생성하고 힌트를 주시오 ! 

튜닝전  :
          select count(ename) 
            from emp;

튜닝 후 :
          (인덱스 생성 후 create index emp_ename on emp(ename) ; )

          SELECT /*+ index(emp emp_ename) */ COUNT (ename)
            FROM EMP ;

or 

  더 성능이 좋게 하려면
          SELECT /*+ index(emp emp_ename) */ COUNT (ename)
            FROM EMP
          WHERE ename > ' ' ; 

 - 더 성능이 좋을 것이라고 판단하는 것은 위의 SQL 은 index full scan이 실행계획에 포함되지만 
 - 아래 SQL은  index range scan 이 실행계획에 포함된다. 

* ename 컬럼에 not null 제약이 걸려있어야 더 확실히 옵티마니져가 index full scan 이나 index fast full scan 을 할 수 있다.  


문제 20. 직업에 인덱스를 걸고 아래의 SQL 의 성능을 높이시오 
튜닝 전 :
          select count(job) from emp ;

튜닝 후 :
          CREATE INDEX EMP_JOB ON EMP(job);

          SELECT /*+ index_ffs(emp emp_job)*/COUNT(job)
            FROM EMP;

   - 이 경우는 어차피 테이블의 모든 row를 보아야 하고 테이블도 작기 때문에 fast full scan을 하는 것이 더 나을 수 있다. 

   * 오라클 버전에 따라서 not null 조약에 따라서 index full scan이 되지 않을 수도 있다. 


문제 21. 아래의 SQL의 성능을 높이시오 ! 

튜닝 전 : 
          select count(*)  
            from sales500;

튜닝 후 :
          create index sales500_rn on sales500(rn) ;

          select /*+ index_ffs (sales500 sales500_rn) count(rn) 
            from sales500;


문제 22. demo를 다시 돌리고 월급에 인덱스를 생성한 후에 월급이 0 이상인 사원들의 월급을 출력하는데 index fast full scan 이 되도록 하시오 ! 

 SELECT /*+ index_ffs(emp emp_sal) */ sal
  FROM EMP
  WHERE sal > 0 ;

* 이 경우  index full scan hint를 주더라도 실행계획은 rang scan으로 짠다.
   ( 더 효율적인 방식으로 인식하여) 

 SELECT /*+ index_fs(emp emp_sal) */ sal
  FROM EMP
  WHERE sal > 0 ;


문제 23. 이름에 EN 또는 IN을 포함하고 있는 사원들의 이름과 월급과 직업과 부서번호를 출력하시오! 

  SQL> select ename, sal, job, deptno 
            from emp 
           where ename like '%EN%' 
                or ename like '%IN%'
    
  * regexp_like 를 사용하여도 된다. 
  
   SQL> select ename, sal 
         from emp
         where regexp_like(ename,'(EN|IN)'); 


문제 24. 아래의 환경을 구성하고 아래의 SQL을 튜닝하시오.

create table customers 
 as select * from sh.customers ;

create index customers_indx1
 on customers (cust_street_address) ;

SELECT  *
 FROM customers
 WHERE cust_street_address LIKE '%Ceneva%'
  or cust_street_address LIKE '%Mcintosh%';
   
  consistent gets -- 1528 

* 각 튜닝 방법 적용 후에 consistent gets의 값 
 
1) where 절에 like 대신 instr 함수를 사용한 경우 

SELECT /*+ index(customers customers_indx1) */ *
 FROM customers
 WHERE INSTR(cust_street_address, 'Ceneva') >0
  or INSTR(cust_street_address, 'Mcintosh') >0 ;
 
 consistent gets -- 389   

2) where절에 like를 사용한 경우

SELECT /*+ index(customers customers_indx1) */ *
 FROM customers
 WHERE cust_street_address LIKE '%Ceneva%'
  or cust_street_address LIKE '%Mcintosh%';

   consistent gets -- 586

3) where 절에 like를 사용하고 index full scan을 시도한 경우 

SELECT /*+ index_fs(customers customers_indx1) */ *
 FROM customers
 WHERE cust_street_address LIKE '%Ceneva%'
  or cust_street_address LIKE '%Mcintosh%';

   consistent gets -- 1462 ( 실제 실행계획이 full table scan 함 )

4) where 절에 like를 사용하고 index fast full scan을 시도한 경우

SELECT /*+ index_ffs(customers customers_indx1)  */ *
 FROM customers
 WHERE cust_street_address LIKE '%Ceneva%'
  or cust_street_address LIKE '%Mcintosh%';

   consistent gets -- 1462 (실제 실행계획이 full table scan 함) 

5) Rowid를 사용해서 테이블 스캔이 일어나도록 하는 방식 

 SELECT /*+ no_query_transformation rowid(customers) */ a.*
  FROM customers a, (SELECT /*+ index(customers customers_indx1) */ ROWID
                       FROM customers
                       WHERE cust_street_address LIKE '%Ceneva%'
                          or cust_street_address LIKE '%Mcintosh%') b
  WHERE A.ROWID = B.ROWID ;

 consistent gets -- 1725

* where 절에 like 사용 보다  instr 사용이 더 빨라진다. 


문제 25. 부서번호의 종류가 몇가지 인지 출력하시오! 

SQL> SELECT COUNT (*)
        FROM (SELECT DISTINCT (deptno) 
                   FROM EMP
                  WHERE deptno IS NOT null) ;

or 

SQL> SELECT COUNT( DISTINCT deptno)
          FROM EMP;


문제 26. 아래의 SQL을 튜닝하시오! 

create index sales500_channel_custid
    on sales (channel_id, cust_id) 

튜닝 전 :
          select /*+ full(sales500) */ * 
            from sales500
          where cust_id = 2869 ;

  consistent gets    -- 4452

튜닝 후 : 
          select /*+ index_ss(sales500 sales500_channel_custid) */ *
            from sales500
          where cust_id = 2869 ;

  consistent gets  -- 200

* 해당 테이블에 alias를 사용하였다면 index 사용시 table 명에도 사용하는 alias를 사용해야 한다. 
     (alias를 사용하지 않으면  hint 가 제대로 사용되지 않는다. ) 

select /*+ index_ss(s sales500_channel_custid) */ *
    from sales500 s
  where cust_id = 2869 ;


문제 27. 아래의 SQL을 튜닝하시오 ! 

( 환경 설정  : 
                     CREATE TABLE mcustsum
                             AS
                       SELECT rownum custno
                                   , '2008' || lpad(CEIL(rownum/100000), 2, '0') salemm
                                   , decode(MOD(rownum, 12), 1, 'A', 'B') salegb
                                   , round(dbms_random.VALUE(1000,100000), -2) saleamt
                          FROM   dual
                     CONNECT BY level <= 1200000 ;

CREATE INDEX mcustsum_IDX2 ON mcustsum(salemm, salegb);

EXEC dbms_stats.gather_schema_stats('SCOTT');



튜닝 전 :

select count(*) 
 from mcustsum t
where salegb = 'A'
and salemm between '200801' and '200812';

-- 3367
-- index fast full scan : 실행계획
-- index range scan : 실제 실행계획

튜닝 후 :

SELECT /*+ index_ss(t mcustsum_IDX2) */ count(*)
 from mcustsum t
where salegb = 'A'
and salemm between '200801' and '200812';
-- 300
-- index skip scan 

* between .. and는 index range scan  과 index fast full scan 이 가능하다. 
* 2가지를 고려해서 테스트 한 후 더 성능이 좋은 것을 사용하면 된다. 


문제 28. 위의  SQL을 힌트쓰지 말고 재작성하는데 Between .. and 가 아니라 IN으로 변경해서 실행하시오 

SELECT /*+ index(t mcustsum_IDX2) */ count(*)
 from mcustsum t
where salegb = 'A'
and salemm IN   ( '200801', '200802','200803','200804','200805','200806','200807','200808','200809','200810','200811', '200812' );
-- 304

* in을 사용해서 실행계획에서 index range scan이 사용 된다. 
* in 사용 시 index_ss(skip scan) 을 사용해도 skip scan 으로 실행계획이 세워지지 않는다. 
* 과거 skip scan 기능이 나오기 전에 사용한 튜닝 방법이다. 


* 점심시간 문제 : 아래의 SQL 을 튜닝하시오!
                            (직업과 월급에 각각 인덱스가 있다.) 

튜닝 전 : 
             select ename,job, sal, deptno
                from emp 
                where job||sal = 'SALESMAN3000' ;
-- 41
-- full table scan 

튜닝 후 :
              select ename, job, sal ,deptno 
              from emp 
               where job = 'SALESMAN'
                    and sal = 3000;

-- 1
-- index range scan 

* 비교 방법
 
db block gets       ┐
consistent gets     ┘  ------------> 메모리 영역 
physical reads    ------------------> 디스크 영역
            
* 인덱스를 각각 생성하지 않아도 가능하다. 
CREATE INDEX emp_job_sal ON EMP(job, sal);


* 오늘의 마지막 문제: 

 아래 SQL 을 튜닝하시오 ! 
 (그룹 함수 쓰지 말고 sal 인덱스만 이용해서 결과를 출력하시오!) 

튜닝 전 :
select max(sal) 
  from emp  ;

튜닝 후 : 

SELECT /*+ index_desc(emp emp_sal) */ sal
 FROM EMP
 WHERE sal > 0
 AND ROWNUM = 1 ;
  ( - 따로 sal 에 index를 걸어주었을 때) 

or 

SELECT sal
 FROM  (SELECT /*+ index_ss_desc(emp emp_deptno_sal) */ sal
          FROM EMP
          WHERE sal > 0)
 WHERE ROWNUM = 1; 
( - 기존에 걸어둔  emp_deptno_sal (deptno, sal) index를 사용하려면 ) 

* 실행 통계를 보기 전에 analizy를 하는 것이 더 정확하다 

* 간혹 SQL gate 버전에 따라서 실행 통계 값이 부정확 할 수 있다. 





1장. 인덱스의 원리와 활용


(1) 인덱스의 구조

  - 인덱스 키 컬럼 + rowid 

  -  null 값에는 index가 생성 되지 않는다.
  -  인덱스의 구조를 보고 싶은데 where 절에 적절하게 이용할 조건이 없다면 ?
    (인덱스를 통하려면 무조건 where 절에 해당 인덱스 컬럼이 존재해야 한다.)
    1) 숫자 > 0
    2) 문자 > ' '
    3) where ename < to_date('9999/12/31','RRRR/MM/DD')

 * 해당 index에서 ename is not null 로 걸면 index full table scan 이 된다.
 
(2) index를 사용하지 못하는 경우
   1) 인덱스 컬럼이 가공이 되면 인덱스 엑세스가 안된다.
       ( where 조건절에 인덱스 컬럼이 가공이 되면 정상적으로 인덱스를 엑세스 할 수 없다.)
  
         - 명시적 형변환
         - 묵시적(암시적) 형변환 
               - 숫자가 우선 순위에 있다.
               - 비교하는값이 문자라면 컬럼 값이 숫자여도 문자로 변환된다.     
               - 묵시적 형변환으로 인하여 함수 기반 인덱스를 생성할 경우도 있다.
                 (그러나 그렇게 추천할 만한 사항은 아니다.)  

     2) 부정형 비교시

       * 오라클 옵티마이저가 제대로 작동하지 않는다면 ( 정렬시 컬럼 값이 정렬되어 있지 않다면) SQL 문에 힌트를 사용한다.
          (힌트 (hint) - 실행계획에 영향을 미치는 명령어)
       * 와일드 카드를 앞에 사용하면 index range scan 이 안된다.

(3) 다양한 인덱스 스캔 방식

     1) Data를 엑세스 하는 방법
            ⑴ 테이블 엑세스 방법
                - rowid 에 의한 data 검색
                - full table scan
        
           ⑵ 인덱스 엑세스 방법
                - index range scan
                - index full scan
                - index unique scan
                - index skip scan
                - index fast full scan
                - index merge scan
                - index bitmap merge scan
                - index join

    2) rowid 에 의한  data 검색
         - 하나의 row를 검색하는 가장 빠른 방법

            ⑴ rowid란
                - row의 물리적 주소 (file#+block#+row#)

            ⑵ rowid hint

              - /*+ rowid(테이블명) */

            예시)
              SELECT ename ,sal
                FROM EMP
             WHERE ROWID = 'AAASlIAAEAAAALuAAL';


 * SQL_실행계획 읽는 법

---------------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |     2 |    40 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP     |     2 |    40 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | EMP_SAL |     2 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
  -> 가장 안 쪽으로 들어가 있는 부분부터 읽으면서 위로 올라간다.


문제 1. 부서번호의 인덱스를 생성하고 부서번호에 인덱스의 구조를 확인하시오!

SQL> create index emp_deptno
            on emp(deptno) ;

SQL> select deptno, rowid
           from emp
         where deptno > 0 ;


문제 2. 아래의  SQL 의 실행계획을 확인해서 SQL을 처리할 때 테이블을 엑세스 하지 않고 인덱스만 엑세스 했다는 것을 확인하시오!

SQL> select deptno, rowid
          from emp
       where deptno > 0 ;


문제 3. 사원 테이블에 월급에 인덱스르 걸고 월급에 걸린 인덱스의 구조를 확인 하시오 !

SQL> create index emp_sal
             on emp(sal) ;

SQL> select sal, rowid
          from emp
          where sal > 0;

* null 값에는 index가 생성 되지 않는다.


문제 4. 사원테이블에 ename 에 인덱스를 걸고 ename에 걸린 인덱스의 구조를 확인하시오!

SQL> create index emp_ename
             on emp(ename);

SQL> select ename, rowid
          from emp
         where  ename > ' ';

* 인덱스의 구조를 보고 싶은데 where 절에 적절하게 이용할 조건이 없다면 ?
    (인덱스를 통하려면 무조건 where 절에 해당 인덱스 컬럼이 존재해야 한다.)

1) 숫자 > 0
2) 문자 > ' '
3) where ename < to_date('9999/12/31','RRRR/MM/DD')

* 해당 index에서 ename is not null 로 걸면 index full table scan 이 걸린다.


문제 5. 입사일에 인덱스를 걸고 입사일에 걸린 인덱스의 구조를 확인하시오!

SQL> create index emp_hiredate
            on emp(hiredate) ;

SQL> SELECT /*+ index_asc(emp emp_hiredate) */ hiredate, rowid
            from emp
        WHERE hiredate < TO_DATE('9999/12/31','RRRR/MM/DD') ;

* 오라클 옵티마이저가 제대로 작동하지 않는다면 ( 정렬시 컬럼 값이 정렬되어 있지 않다면) SQL 문에 힌트를 사용한다.
* 힌트 (hint) - 실행계획에 영향을 미치는 명령어


문제 6. 월급이 1000에서 3000 사이인 사원들의 이름과 월급을 출력하는데 월급이 높은사원부터 출력하시오!

SQL> SELECT /*+ index_desc(emp emp_sal) */ ename, sal
            from emp
        WHERE sal BETWEEN 1000 AND 3000  ;

*  order by 절은 성능을 느리게 한다.
 

문제 7. 1981년도에 입사한 사원들의 이름과 입사일을 출력하는데 최근에 입사한 사원부터 출력하시오!

SQL> SELECT /*+ index_desc(emp emp_hiredate) */ ename,hiredate
        FROM emp
      WHERE hiredate between TO_DATE('1981/01/01','RRRR/MM/DD') AND TO_DATE('1981/12/31','RRRR/MM/DD');

* 위의 SQL이 Full table scan 이유
    - where  조건절에 인덱스 컬럼이 가공이 되면 정상적으로 인덱스를 엑세스 할 수 없다.


문제 8. 월급이 3000인 사원들의 이름과 월급을 출력하시오!

explain plan for
  SELECT ename, sal
  FROM emp
  WHERE sal=3000;

select * from table(dbms_xplan.display);


문제 9. 이름이 SCOTT 인 사원의 이름과 월급과 직업을 출력하는 실행계획을 출력하시오 !

SQL> explain plan for
    2   select ename, sal
    3  from emp
    4  where ename='SCOTT';

SQL> select * from table(dbms_xplan.display);


PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 106684950

-----------------------------------------------------------------------------------------
| Id  | Operation            | Name    | Rows    | Bytes | Cost (%CPU)| Time    |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |        |     1 |    20 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP    |     1 |    20 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN        | EMP_ENAME |     1 |    |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

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

   2 - access("ENAME"='SCOTT')

Note
-----
   - dynamic sampling used for this statement (level=2)


문제 10. 아래의 SQL 을 튜닝하시오 !

튜닝 전 :

select ename, sal *12 연봉
  from emp
where sal*12 =36000;

튜닝 후 :

SELECT /*index_desc(emp emp_sal)*/ename, sal*12 연봉
FROM EMP
WHERE sal = 36000/12;


문제 11. 아래의 SQL을 튜닝하시오!

create index emp_job
on emp(job);

튜닝 전 : 
select ename, job, sal
                from emp
              where substr(job,1,5)='SALES';

튜닝 후:
SELECT /*+ index(emp emp_job) */ ename, job, sal
  FROM EMP
  WHERE job LIKE 'SALES%' ;

※ 와일드 카드를 앞에 사용하면 index range scan 이 안된다.


문제 12. 아래의 데이터를 입력하고 오늘 입사한 사원의 이름과 입사일을 출력하시오!

SELECT ename, hiredate
FROM EMP
WHERE hiredate >= to_date('16/01/08','RR/MM/DD')
AND hiredate < TO_DATE('16/01/08','RR/MM/DD')+1;


문제 13. 아래의 SQL 을 튜닝하시오 !

- 조건
CREATE TABLE EMP700
  (ename VARCHAR2(20),
   SAL VARCHAR2(20));


CREATE INDEX emp700_sal
ON emp700(sal);


튜닝전 :

select ename, sal
from emp700
where sal = 3000;

* 위의 경우 실제 실행 방법

Plan hash value: 3880125747

----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |       |       |     3 (100)|          |
|*  1 |  TABLE ACCESS FULL| EMP700 |     1 |    24 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------

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

   1 - filter(TO_NUMBER("SAL")=:SYS_B_0)

Note
-----
   - dynamic sampling used for this statement (level=2)

튜닝후:

SELECT /*+ index(emp700 emp700_sal) */ ename, sal
from emp700
where sal = '3000';

* SQL*plus 에서 실제 실행계획을 보는 명령어

-  select *
from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST +alias +outline +predicate'))
/


문제 14. 아래의 SQL의 결과가 출력되겠는가?

튜닝 전 :
select ename, sal
from emp
where sal like '30%';

* 해당 경우는 모델이 잘 못 된 경우로 해당 상태는 프로그램 수정으로는 튜닝이 안된다.
    So. 프로그램 수정이 아닌 다른 방법을 사용해야 한다. 

튜닝 후 : 
( 함수 기반 인덱스를 생성한다. )
create index emp_sal_func
on emp(to_char(sal));

select /*+ index(emp emp_sal_func) */ename, sal
from emp
where sal like '30%';

select /*+ index(emp emp_sal_func) */ename, sal  
from emp 
where sal like :"SYS_B_0"

( 바인드 변수 사용 )

Plan hash value: 3983152063

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |       |       |     4 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP          |    16 |   672 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | EMP_SAL_FUNC |    16 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

문제 15. 아래의 data를 입력하고 이름이  JANE 인 사원의 이름과 월급과 직업을 출력하시오!

튜닝 전:

insert into emp(empno, ename, sal)
valuse(2345, '   JANE  ', 4600);

튜닝 후

create index emp_ename_func
on emp(TRIM(ename));

SELECT /*+ index(emp emp_ename_func)*/ ename, sal, job
  FROM EMP
  WHERE TRIM(ename) = 'JANE';


문제 16. 이름이 EN 또는 IN을 포함하고 있는 사원들의 이름과 월급, 직업과 입사일을 출력하시오!

-- 튜닝 전

SELECT ename, sal, job, hiredate
FROM EMP
WHERE ename LIKE '%EN%'
or ename LIKE '%IN%';

OR

SELECT ename, sal, job, hiredate
FROM EMP
WHERE regexp_like(ename,'(EM|IN)');

-- 튜닝 후

SELECT ename, sal, job, hiredate
FROM EMP
WHERE ROWID in (SELECT /*+ index(emp emp_ename) */ rowid
                  from emp
                where ename like '%EN%' or ename like '%IN%') ;

or

SELECT /*+ no_query_transformation rowid(emp) */ ename, sal, job, hiredate
  FROM EMP e,(SELECT /*+ index(emp emp_ename)   */ rowid
                  from EMP
                where ename like '%EN%' or ename like '%IN%') b
WHERE e.ROWID = b.rowid  ;

* 둘의 실행 계획이 다르다. 
  - 바로 검색보다는 서브쿼리르 사용해서 rowid를 먼저 조회하고 검색하는 것이 빠를 수 있다.
  - 어떤 실행계획이 달라서 실행계획을 보고 계산할 수 있을 때까지는 어느것이 더 성능이 좋을 지 모른다.


+ Recent posts