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 

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


나는 용기 있는 사람이다.

 나이가 먹도록 자신이 하고 싶은 일을 위해서 공부만 하고 있는 것이 미안했고 죄스러웠었다. 그 공부는 또한 석사 박사가 되기 위한 공부는 아니었다. 물론 석사 박사가 되기 위한 공부를 하고 싶었다. 자신도 있었고 교수님들에게 공부를 좀 더 해보기를 권하기도 했으며, 무엇보다도 내가 책을 읽고 생각하고 그러한 내용들 다른 사람들과 이야기 하는 것을 너무 좋아했다. 특히 책을 읽고 생각하고 글을 쓰는 것을 너무 좋아해서 그럴 수만 있다면 매일 그러고 싶었다. 그러나 집안 형편상 그럴 수는 없었다. 그때 이후부터였다. 내가 무언가 배우는 것을 열망하기 시작했던 것은 닥치는 대로 책을 읽었으며, 흥미가 있는 것들을 배우는 것에는 빠지기 시작했다. 아마도 그것이 정신적인 나의 위안이었던 것 같다. 그렇게 무언가 배우고 지식을 쌓은 일이 너무나도 재미있었고 그러한 지식과 지혜들이 사회에 어느 정도 적용될 수 있는지 알고 싶었고 그러한 지혜로 세상을 좀 더 좋은 곳으로 바꾸고 싶었다.

 그러나 돈을 버는 것은 중요했다. 하지만 그렇다고 해서 하기 싫은 일을 억지로 하면서 돈을 버는 것은 너무 힘들었다. 돈은 벌 수 있으나 그 시간에 나는 행복하지 못했고 시간이 아깝게 느껴졌다. 그래서 나는 계속해서 공부할 수 있으며, 끊임없이 생각할 수 있고, 세상에 영향력을 줄 수 있으며, 마지막으로 중요한 그러면서 경제 활동이 되는 것이 무엇인지 찾아보았다.

 그래서 찾은 것이 분석에 관련한 업무였다. 나는 꿈에 부풀었었다. 분석은 잘할 수 있을 것 같았다. 어느 정도 지식이 쌓여서 펼쳐지는 남들과는 다양한 생각들을 검증해 볼 수 있으며 계속해서 공부해야만 했고 내가 만들어낸 정보는 사람들에게 중요한 영향을 줄 수도 있다고 생각했다. 그래서 나는 인문학적인 소양을 향상하려고 했다. 기술이란 결국 손에 익으면 된다고 생각했다. 그보다 더 중요한 것은 분석을 왜 해야 하는지 분석을 해야 하는 포인트가 어디인지 그리고 그러한 생각들이 논리적으로 이루어져 있는지에 대한 인문학적인 소양이라고 생각했기 때문이다. 그러나 나는 잘 못 알고 있었다. 기술적인 측면은 취업시장에서 매우 중요한 부분이었다. 나는 그 부분을 간과하고 있었다.

 인문학적인 소양을 키웠으니 기술적인 측면을 키우면 되었지만 그럴만한 시간이 없다고 생각했다. 내가 가지고 있는 것은 측정이 어려운 만큼 다른 사람들이 가지고 있는 측정 가능한 영역에서 비교되는 내가 보였으며, 분석을 하고 싶다는 열망은 가지지 못한 것에 대한 자괴감으로 인하여 더욱 큰 고통이 되었다. 그리고 가장 원했던 것이 아닌 자꾸 차선을 선택하고자 하였다. 그러나 역시나 내 자존심은 내 생각보다 강했다. 이를 자존심이라고 해야 할지 열망이라고 해야 할지 잘 모르겠다. 그러나 그 무엇이 다시금 나를 도전하게 되었다. 남들보다 불리한 상황으로 시작하게 되었다.

  먼저 나이가 많았다. 다른 시작하는 사람들에 비해 나이가 많았다. 그래서 자는 시간을 줄였다. 남들이 자는 시간, 먹는 시간, 노는 시간에 그만큼 공부를 하였다. 남들이 뭐라고 하여도 나는 그게 좋았다. 물론 나도 쉬는 시간은 있었지만, 같이 배우며 배운 내용에 관해서 토론하고 이야기하는 시간은 그보다 더 좋았다. 또 다른 나의 불리한 상황은 가난이었다. 집안은 나를 지원해줄 수 있는 형편은 아니었고 내가 모아둔 돈도 그리 많지 않았다. 그래서 지출을 줄였다. 옷이나 신발 그리고 그 밖에 기본적인 지출을 최소한으로 두었다. 그리고 책은 도서관을 이용했다. 인터넷을 뒤졌다. 사람들에게 도움을 구했다.

 그렇게 노력했고 지금도 노력하고 있다. 그래서 나는 용기 있는 사람이다. 나는 강한 사람은 아니다. 강한 사람이었다면 어려운 현실에 우울함을 느낄 일도 없고, 포기하고자 하는 생각에 번민이 쌓이지도 않을 것이다. 그러나 나는 노력하는 동안에도 흔들렸고 힘이 들었다. 그래서 나는 강한 사람은 아니었지만, 용기는 있는 사람이었다. 용기가 없었다면 이처럼 조금은 늦은 나이에 다시금 도전이라는 시도를 하지 않았을 것이다. 그것도 불리한 것밖에 없는 상황에서 말이다. 나는 아직도 갈 길이 멀다. 어쩌면 이러한 어려움이 조금 더 지속될지 모르겠다. 그래도 나는 나를 앞으로도 패배자라고 생각하지 않겠다. 나는 용기 있는 사람이다. 패배자와 용기 있는 사람의 차이는 이처럼 종이 한 장의 차이로 구별된다

 

  

''나'의 이야기' 카테고리의 다른 글

나의 이야기 (1)  (0) 2019.12.24
글을 쓴다  (1) 2019.12.15
[신념 1.] 확식이 없다면 가장 기본을 먼저 취하라  (0) 2014.11.10
스스로를 믿자  (0) 2014.10.29
뭐가 '잘 될 거야!'냐..  (0) 2014.10.26

종이 한 장의 차이

 사람들이 명승부를 보면서 간혹 종이 한 장의 차이였다라는 말을 하곤 한다. 나도 자주는 아니지만 가끔 사용하는 문장이다. 그런데 이 종이 한 장의 차이는 과연 무슨 차이일까? 사람들은 무슨 일을 할 때 흔히 확률을 물어본다. 그 승패의 확률은 어떻게 돼 ? 이길 가능성은 얼마나 돼?라고 물어본다. 그러나 그것은 집단 안에 많은 사람들이 시도했을 때의 결과 값일 뿐 개개인은 그 통계와는 다른 양상을 띤다. 개개인에게 그 승패의 결과는 성공 또는 실패의 50% 확률의 게임이다.

 여기서 종이 한 장 차이가 발생한다. 그 개인이 어떠한 승부에서 이길 확률은 50%에서 51%가 되는 순간 승부가 결정되며 우리는 한순간의 51%를 위하여 매일 같이 노력하는 것이다. 그리고 그 1%는 종이 한 장의 차이이다. 비록 어떠한 승부에서 큰 차이가 나서 지게 되더라도 그것은 결과이며 시작은 항상 50% 확률로 시작하게 된다. 이러한 생각을 하게 되면 50% 확률이라는 의미를 조금 다르게도 생각해 볼 수 있게 된다. 내가 아무리 자신이 있더라도 확률이 50%라고 생각하게 되면 자만심을 줄일 수 있다. 또한, 내가 아무리 자신이 없더라도 확률이 50%라고 생각하게 되면 자신감을 가질 수 있다

  50%는 어쩐지 아리스토텔레스의 중용을 떠오르게 한다. 중학교 시절 배웠던 아리스토텔레스의 최선의 선 중용은 혈기가 왕성하던 시절에 그저 어느 한쪽에도 치우치지 못한 어중간함으로 보였다면 세월이 지나면서 느끼게 된 중용현명한 삶 그 자체를 느끼게 했다. 치우침은 극과 맞닿아 있다. 그리고 극과 극은 서로 종이 한 장 차이이다. 궁극의 선이라고 하여도 인간의 다양성의 대한 포용이 없이 그것을 타인에게 강요하는 순간 그것은 궁극의 선과 맞닿아 있는 어둠을 접하게 된다. 중용이라는 단순히 가운데 있는 것이 아니다. 중용을 지키기 위해서는 먼저 서로의 끝을 알 만큼 지혜가 있어야 한다. 그러한 지혜가 없다면 가운 지점을 찾을 수 없다. 또한, 중용은 치우치지 않아야 한다. 서로의 끝을 알고 가운데를 찾았다고 하더라도 균형을 잡을 수 없다면 중용을 지킬 수 없다. 또한 중용은 포용할 줄 알아야 한다. 가운데라는 의미는 양쪽 끝을 절단하는 구분점이 아닌 양쪽의 끝을 이어준다는 의미이다. 이렇듯 중용이란 지혜, 균형 포용의 3박자의 어우러짐이다. 이런 것들을 생각한 후에 나 또한 중용을 지키고자 하지만 쉽지 않은 일이다.

 사실 이 종이 한 장 차이에 대한 글은 드라마 동네변호사 조들호에서 박신양을 보고 시작되었다. 동네변호사 조들호에서 2화 마지막은 자신에 말을 들어준 사람이 조변호사 밖에 없었다는 피고인의 이야기를 들은 조들호(박신양)의 표정을 클로즈업하면서 끝이 나게 된다. 그런데 이 마지막 표정에서 나는 종이 한 장의 차이를 느꼈다. 그 표정에서 조들호의 복잡한 심경을 느끼게 하였다. 마지막 부분이며 그냥 무심코 보면 드라마의 연출상 마지막 부분을 만들기 위한 단순한 클로즈업일 수도 있다. 그리고 많은 드라마에서 배우들의 얼굴을 클로즈업하면서 드라마를 마무리한다. 그러나 그러한 마무리 표정으로 복잡한 감정을 느끼게 하는 배우는 얼마나 있을까? 세상에는 많은 배우들이 있다. 그리고 그들은 서로 다른 모습을 지니고 있다. 그리고 난 솔직히 누가 절대적으로 연기를 잘하고 못 하는지에 대한 차이를 잘 알지도 못한다. 대배우와 신인배우의 연기력은 차이가 날 수도 있겠지만, 그 차이가 얼마나 나는지 알 수도 없으며 인기에 대한 후광효과 또한 연기력에 포함된다면 더욱더 그 차이를 알기 힘들다. 그러나 열심히 하는 배우라면 미세한 차이도 신경을 쓸 것이다. 모든 사람이 느낄 수 없다고 하더라도 누군가는 미세한 차이를 위해서 계속해서 노력해온다. 그리고 그 미세한 차이는 정확히 언제 인지는 사람인지라 알 수 없지만, 언제가 드러나게 된다. 나는 여기서 박신양이라는 배우를 칭찬하고 싶은 것이 아니다. 그저 박신양이라는 배우는 연기를 위해서 매우 노력하는 사람이라는 것을 방송을 통해서 알게 되었고 그 노력에 대한 값어치를 말하고 싶은 것이다. , 박신양이라는 배우는 많은 사람들이 알만한 예시이며 결국에는 누군가 미세한 차이를 위해 노력하는 사람들 전부를 의미하는 것이다.

 결국, 미세한 차이는 종이 한 장의 차이이다. 나는 이러한 종이 한 장의 차이를 위해서 오늘도 최선을 다하고 있다. 하루에 5시간을 노력하는 사람과 10시간을 노력하는 사람이 처음에는 같은 결과가 나왔다고 하자. 그렇다고 해서 그 둘은 같은 사람은 아니다. 그 둘의 차이는 언젠가 종이 한 장의 차이로 밝혀질 것이다. 이렇듯 종이 한 장의 차이는 50%의 확률을 1%로 만드는 것이지만 결국의 승패를 결정짓는 것은 그 1% 종이 한 장의 차이로 결정짓게 된다. 1%의 힘을 믿으며 나는 오늘도 최선을 다한다.

 

''키워드'로 생각해보기' 카테고리의 다른 글

사랑 감기  (1) 2020.03.01
시간의 상대성  (0) 2019.12.28
온천  (0) 2019.12.24
의문  (0) 2019.12.16
"고독"  (1) 2014.11.30


  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 



- 함수를 왜 배워야하는가? 

ex)
우리반 테이블에서 전공, 전공별 인원수를 출력하시오!

우리반 테이블에세 가장 나이가 어린 학생의 이름을 출력하시오!

메일 도메인, 메일 도메인별 건수를 출력하시오! 

- 함수란 무엇인가?



- 함수의 종류 2가지? 


1. 단일행 함수
   - 문자
   - 숫자
   - 날짜
   - 변환
   - 일반 

1) 문자 함수 
   (1) 대소문자 변환 함수 
       - lower : 소문자로 출력하는 함수
       - upper : 대문자로 출력하는 함수
       - initcap: 첫번째 철자는 대문자 나머지는 소문자로 출력하는 함수 

   (2) 문자 조작 함수 
       - concat : 두개의 컬럼의 값을 연결하는 함수
       - substr : 특정 철자를 잘라내는 함수
       - length : 철자의 길이를 출력하는 함수 
       - instr : 특정 철자의 위치를 출력하는 함수
       - lpad : 왼쪽에 특정값을 채워넣는 함수 
       - rpad : 오른쪽에 특정값을 채워넣는 함수 
       - trim : 특정 철자나 값을 잘라내는 함수 
       - replace : 특정 철자로 변경하는 함수 

2) 숫자 함수 
      - round : 반올림하는 함수
      - trunc : 잘라내는 함수
      - mod : 나눈 나머지값을 출력  

3) 날짜 함수 
     - between month : 해당 기간 동안의 몇달이 있었는지 출력 
     - add-months: 기준 날짜에서 입력한 달 수 만큼 지난 날짜를 출력
     - next-day: 기준 날짜에서 해당 요일이 오늘 날짜를 출력 
     - last-day: 기준 날짜에서 해당 달의 끝까지 몇일이 남았는지 출력

4)변환 함수 / data type 을 변환
    
   (1) data type 
         - 숫자
         - 문자
         - 날짜 
   (2) 형변환 2가지 (하나의 데이터가 아니라 형식을 바꾼다고 생각해라)
        1. 암시적 형변환 : 오라클이 알아서 형변환 수행 
           예시) select ename, sal 
                     from emp
                     where sal = '3000'
                               숫자   문자 
                     - 이는 원래 실행 되어서는 안된지만 오라클 내부적으로 data type을 자동 변환하여 실행된다.
                     - 오라클은 숫자를 문자보다 더 높은 우선 순위에 두기 때문에 문자를 숫자로 변환한다.  (숫자>문자)
                     - BUT. 이런 식의 코드는 실행은 되지만 효율이 떨어져 수정해야 한다. 

        2. 명시적 형변환:
           - to_char :문자로 형변환하는 함수 
                            날짜 ---> 문자 
                            숫자 ---> 문자 

           - to_number : 숫자로 형변환하는 함수 
           - to_date :날짜로 형변환하는 함수 
  
  5) 일반 함수 
     - NVL 함수: null 값이 숫자를 변화하여 출력하라 
     - NVL2
     - decode 함수
     - case 함수 

2. 복수행 함수
   - max
   - min
   - count
   - avg
   - sum


문제 92. 나이대가 20대인 학생들의 이름과 나이와 주소를 출력하는데, 나이가 높은 학생부터 출력하시오!

SQL> SELECT ename, age, address
           FROM EMP2
           WHERE age BETWEEN 20 AND 29
           ORDER BY age DESC;


문제 93. 서울에서 살지 않는 학생들의 이름과 학원까지의 시간을 출력하는데 시간이 가장 오래 걸리는 학생부터 출력하시오! 

SQL> SELECT ename, distance
           FROM EMP2
           WHERE address NOT LIKE '%서울%'
           ORDER BY distance DESC;


문제 94. 이름에 EN 또는 IN이 포함되어져 있는 사원의 이름과 월급을 출력하시오!(EMP 테이블) 
   
  SQL> SELECT ename, sal
             FROM EMP
             WHERE ename LIKE '%EN%' OR
                        ename LIKE '%IN%';

*Regexp_substr 활용하여 더 간편하게 검색 가능

SQL> select ename, sal 
         from emp
         where regexp_like(ename,'(EN|IN)');
  - 이 쿼리에서 '|'는 또는을 의미하며 계속 추가할 수 있다. 
  - regexp_like 는 '%'(와일드 카드)를 쓸 필요가 없다.  

* MS_SQL에서는 regexp를 사용할 수 없다.



문제 95. 이름, 월급을 출력하는데 월급을 암호화해서 출력하시오! 월급의 숫자 234를 각각 *로 출력하시오 

SQL> SELECT ename,regexp_REPLACE(sal,'[2-4]','*') 
           FROM EMP;

* regexp/정규 표현식 
  
* 이 경우 이런 코드도 가능하다. 
  SELECT ename,regexp_REPLACE(sal,'(2|3|4)','*') 
  FROM EMP;

만약 연속 된 숫자가 아니였다면, 이런 식으로 입력가능 할듯 하다.

* MS_SQL 에서는 replace에 정규표현식을 사용할 수 없어서 위와 같은 결과를 내려면 아래와 같이 중첩으로 사용해야 한다. 

select ename,replace(replace(replace(sal,'2','*'),'3','*'),'4','*')
 from emp ;


문제 96. 이름, 월급, 보너스(sal,*02) 를 출력하시오! 
             컬럼별칭을 사용해서 월급*0.2가 보너스로 컬럼명이 출력되게하시오! 
             (보너스가 일의 자리에서 반올림되게 하시오!)

SELECT ename, sal, ROUND (sal*0.2,-1) AS 보너스
  FROM EMP;

*ROUND / 반올림 하는 keyword
  TRUNC / 버림하는 ketword

예시) select round(756.57,1), trunc (756.57,1)
         여기서 '.'은 기준으로 '0'이 된다. 
         뒤로 가면서 1,2, ... 
         앞으로 가면서 -1, -2, -3, 으로 된다. 

Tip. dual 이란?
  - dumy table 인데 select절의 결과 값을 보기 위한 가상의 테이블 
   (실제 테이블을 일력하면 같은 숫자가 대체 되어 나온다.)
 

주의! 별칭을 정해주기 전에 ROUND로 묶어준다. 

* MS_SQL도 반올림과 버림을 할 수 있지만 Oracle과 차이가 있다. 
  - round는 소수점 몇 번째까지 지정해 줄 수 있지만 버림음 floor 함수를 사용해서 정수만 나타낸다.  
   예시 ) select round(756.57,1) ;
            - 이 문장은 756.5 으로 결과가 나타남 

     
          select FLOOR(756.57 ,1) ;
            - 이 문장은 뒤에 1 때문에 문자 오류가 발생하고, 1을 없애면 756 만 출력된다. 



문제 97. 우리반 테이블에서 이름과 나이를 출력하는데 이름과 나이컬럼 옆에 나이가 짝수인지 홀수인지를 출력되게 하시오!

SQL> SELECT ename, age, MOD(age,2) AS 짝수홀수여부
           FROM EMP2;

*MOD/ 해당 숫자로 나누어서 나머지를 구하는 keyword

예시) select mod(10,3)
          from dual;
     - 10을 3으로 나눈 나머지 값 
      (실무에서는 해당 숫자가 짝수인지 홀수인지 판별할때 주로 사용한다.)

* MS_SQL 은 Oracle의 Mod 함수를 지원하지 않는다. 
   - 그래서 위의 Oracle 쿼리처럼 구현하기 위해서는 %를 사용해야 한다. 
    예시 ) SQL> SELECT ename, age, age%2 AS 짝수홀수여부
                     FROM EMP2;



문제 98.  오늘날짜를 출력하시오! 

SQL> select sysdate
           from daul;

*날짜 - 숫자 =   날짜 (date)
*날짜 + 숫자 =  날짜 (date)
*날짜  - 날짜 =   숫자 (for)


* MS_SQL에서 오늘의 날짜 출력 법 
  SQL> select SYSDATETIME() ;



문제 99 우리반 테이블에서 이름, 내가 태어날 날부터 오늘까지 총 몇일 살았는지 출력하시오!

SQL> SELECT ename, ROUND((sysdate - birth),0)
            FROM EMP2;

* '0'을 쓰지 않아도 default 값이 0으로 자동 출력된다. 

* MS_SQL에서는 이런식의 날짜 계산은 사용할 수 없다. 
  - 날짜 변환 시 문자 형식으로 변환되는데 문자끼리는 연산이 되질 않는다. 
  - 그래서 dateadd, datediff 같은 날짜 계산 함수를 써서만 날짜 계산이 가능하다. 



문제 100. 우리반 테이블에서 이름, 내가 태어난 날부터 오늘까지 총 몇주를 살았는지 출력하시오! 

SQL> SELECT ename, ROUND((sysdate - birth)/7,0)
           FROM EMP2;



문제 101. 우리반 테이블에서 이름, 내가 태어난 날부터 오늘까지 총 몇달을 살았는지 출력하시오!

SQL> SELECT ename, MONTHS_BETWEEN(SYSDATE, birth)
            FROM EMP2;

* 날짜 함수 
      1. months_between / 주의 사항은 앞에 변수 값에 최신 날짜를 입력해야 한다. 

* MS_SQL에서 날짜 계산 함수 형식
  - datediff( 시간단위구분자, 시작시간, 종료시간 )

select DATEDIFF ( mi , getdate() , getdate()+1 ) ==> 분
select DATEDIFF ( s , getdate() , getdate()+1 ) ==> 초 
select DATEDIFF ( hour , getdate() , getdate()+1 ) ==> 시간 (1일)
select DATEDIFF ( day , getdate() , getdate()+1 ) ==>  일 (1일)
select DATEDIFF ( month , getdate() , getdate()+31 ) ==> 개월 (31일)
select DATEDIFF ( year , getdate() , getdate()+730 ) ==>  년 (730일)

예시) (위와 같이 출력하고자 할때) 
     SQL> select datediff(month, hirth, getdate())
               from emp2 ;



문제 102. 사원 테이블에서 이름,  입사후 부터 그동안 받았던 총 급여를 출력하시오! (sal이 월급이라고 할 때)


SQL> SELECT ename, sal*ROUND(MONTHS_BETWEEN(SYSDATE,hiredate))
           FROM EMP;

* MS_SQL에서 출력 시 

  SQL> select ename, sal*datediff(month, HIREDATE, getdate())
           from emp ;



문제 103. 오늘부터 100달 뒤에 돌아오는 날짜를 출력하시오! 
 
SQL> select add_months(sysdate, 100)
           from dual;

* add_months/ 앞으로 해당 달 수 이후의 날짜 

* MS_SQL에서 앞으로 해당 달 수 이후의 날짜 구하는 법
  - MS_SQL에서는 Oracle 의 add_month 와 같은 dateadd 함수가 있다. 
    예시 )  select ename,dateadd(month,100,hiredate)
               from emp ;



문제 104. 오늘부터 앞으로 돌아올 금요일에 날짜를 출력하시오! 

SQL> select next_day(sysdate,'friday')
           from dual;

주의! 지정 요일에는 ' ' 표시를 꼭 해야 한다. 

* MS_SQL에서는 next_day와 같은 기능이 없다. 
   - 그래서 특정 날짜의 요일을 찾고 추론해야 한다. 
     예시 )   select datename(dw,getdate());
              
               OR 

               select datename(weekday,getdate());
             
             위의 쿼리를 돌리면 해당 요일이 나온다 거기서 원하는 요일까지 추론해야 한다. 




문제 105. 오늘부터 200달 뒤에 돌아오는 목요일의 날짜를 출력하시오 

SQL>  select next_day(add_months(sysdate, 200),'thursday')
           from dual;

* MS_SQL 에서 몇 달 후 돌아오는 날짜 출력하기 
   - 이번에도 위와 같이 특정 날짜를 찾고 추론해야 한다. 

SQL > select datename(dw,dateadd(month,200,getdate()))


문제 106. 오늘부터 요번달 말일까지 총 몇일 남았는지 출력하시오! 

SQL> SELECT LAST_DAY(SYSDATE)-SYSDATE
            FROM dual;


* MS_SQL은 LAST_DAY를 지원하지 않는다. 
   - 그래서 날짜 비교함수를 이용해서 해당 말일까지 몇일이 남았는지 출력해야 한다.
     
     예시 ) select datediff(day, getdate(), dateadd(month,1,getdate())-day(getdate()));
                   ( dateadd(month,1,getdate())-day(getdate()) 는 해당달의 마지막 날을 표시해준다.) 


문제 107. 이름과 월급을 출력하는데 월급을 출력할때에 천단위를 표시하시오! 

 SQL> select ename, to_char(sal), to_char(sal,'999,999')
   from emp;

* to_char(sal) / sal 의 type 이 문자로 바뀜
    따라서.
   select ename, to_char(sal) AS aaa
     from emp 
     ORDER BY aaa ASC;
    이 코드는 숫자의 크기로 정렬되지 않는다. 

* to_char(sal, '999,999') 
   - 이는 천단위로 끊어주게 해준다. 
   - 여기서 '9'는 0~9까지 무슨 수가 와도 관계 없다는 의미 

* MS_SQL은 천단위 표시를 하기 위해서는 2번의 형변환이 필요하다 
   - 첫번째 money로 형변화, 두번째 문자로 형변환이다. 
     예시 ) select ename, replace (convert(varchar,cast(sal as money),1) , '.00','')
              from emp ;
      
           위의 예시를 설명하자면 
              1) 먼저 숫자를 money 형으로 바꾸어 주어야 한다. 
              2) 두번째는 money형을 문자로 바꾸어 주어야 한다. 
                 (이때 convert에 3번째 인자 값은 money 형의 형식을 구분한다. '1' 의 형식은 천의자리에 콤마이다. )
              3) 필요 없는 소수점을 없애주기 위해서 replace를 사용한다. 
 
* money의 문자로 변환하기 위한 style 값의 차이 비교 

출력

0(기본값)

소수점 왼쪽의 세 자릿수마다 쉼표를 사용하지 않으며, 소수점 오른쪽에 두자리가 나타난다
(
: 1234.56)

1

소수점 왼쪽의 세 자릿수마다 쉼표를 사용하며, 소수점 오른쪽에 두자리가 나타납니다.
(
:1,234.56)

2

소수점 왼쪽의 세 자릿수마다 쉼표를 사용하지 않으며, 소수점 오른쪽에 네 자리가 나타납니다.
(
:1234,5678)



문제 108. 이름과 총급여 (SAL*25000000) 을 출력하는데, 총급여를 출력할때에 천단위와 백만단위를 출력되게하시오!

SQL> SELECT ename, TO_CHAR((sal*25000000),'999,999,999,999')
           FROM EMP; 

주의! 숫자를 끊어줄 때 범위를 실제 값보다 적게 잡아주면 모든 숫자가 '#'로 표시 된다.

* 숫자에 to_char 함수 사용 


문제 109. 이름, 입사일, 입사한 요일을 출력하시오! 

  SQL> SELECT ename, hiredate, TO_CHAR(hiredate,'day')
              FROM EMP;

* 지난 10년 동안 살인 사간이 가장 많이 일어난 요일이 무엇인가?
* 날짜 형식 
  - YYYY , RRRR, YY, RR : 숫자 년도 
  - YEAR : 영문으로 년도 출력
  - MM : 달
  - MONTH : 달을 영문으로 출력
  - DD : 일
  - DAY, DY : 요일 (DY는 요일이 요약해서 출력 됨)


문제 110. 현재 세션의 날짜 형식을 확인하시오! 

SQL>SELECT *
           FROM nls_session_parameters;


문제 111. 81년 12월 11일에 입사한 사원의 이름과 입사일을 출력하시오! 

SQL> SELECT ename, hiredate
            FROM EMP
             WHERE hiredate = '11/dec/81';

*현재 세션의 날짜 형식을 RR/MM/DD 로 변경
   ALTER SESSION SET nls_date_format='RR/MM/DD';


문제 112. 현재 세션의 날짜 형식을 아래와 같이 변경하고, 81년 12월 11일에 입사한 사원들의 이름과 입사일을 출력하시

SQL> SELECT ename, hiredate
            FROM EMP
            WHERE hiredate = '81/12/11';


 * RR  과 YY 의 차이
                 RR                                   YY
                 81                                    81
         1981       2081                 1981          2081
                2015                               2015
        현재 연도에서 가장           현재 세기의 연도
        가까운 연도 선택

*주로 RR 을 사용한다. 

문제 113. 현재 세션의 날짜 형식이 어떻게 되는 상관없이 81년 12월 11일에 입사한 사원들의 이름과 입사일을 출력하시오 !


1. to_char
         
        select ename, hiredate
            from emp
            where to_char(hiredate, 'RR/MM/DD')='81/12/11';

* 그러나 이럴 경우 모든 데이타를 변화해야 하기 때문에 성능이 떨어지게 된다. 

2. to_date
         
         select ename, hiredate
            from emp
            where hiredate = to_date('81/12/11','RR/MM/DD');

* 이럴 경우 성능에 영향을 주지 않으면서 정확하게 검색할 수 있다.


문제 114. 87년 5월 6일 부터 93년 3월 4일 사이에 태어난 학생들의 이름과 생일을 출력하시오!

SQL> SELECT ename, birth
           FROM EMP2
           WHERE birth BETWEEN TO_DATE('87/05/06','RR/MM/DD')
                                      AND TO_DATE('93/03/04','RR/MM/DD');


* MS_SQL은 to_data 함수를 사용할 수 없다. 
  - 그래서 실제 날짜 데이터 형식과 다른 형식으로 검색해서 결과를 보고 싶다면 convert 함수를 이용해야 한다. 

    예시 )  select ename, hirth 
               from emp2 
            where hiredate between convert(varchar,'1987/05/06',111) and convert(varchar,'1993/03/04',111) ;

  
* convert 시 날짜 형식 변화 시 style 식

세기 포함 안함(yy)

세기 포함(yyyy)

표준

입력/출력**

-

0 또는 100 (*)

기본값

mon dd yyyy hh:miAM(또는 PM)

1

101

USA

mm/dd/yy

2

102

ANSI

yy.mm.dd

3

103

영국/프랑스

dd/mm/yy

4

104

독일

dd.mm.yy

5

105

이탈리아

dd-mm-yy

6

106

-

dd mon yy

7

107

-

Mon dd, yy

8

108

-

hh:mm:ss

-

9 또는 109 (*)

기본값 + 밀리초

mon dd yyyy hh:mi:ss:mmmAM(또는 PM)

10

110

USA

mm-dd-yy

11

111

일본

yy/mm/dd

12

112

ISO

yymmdd

-

13 또는 113 (*)

유럽 기본값 + 밀리초

dd mon yyyy hh:mm:ss:mmm(24h)

14

114

-

hh:mi:ss:mmm(24h)

-

20 또는 120 (*)

ODBC 표준

yyyy-mm-dd hh:mi:ss(24h)

-

21 또는 121 (*)

ODBC 표준(밀리초)

yyyy-mm-dd hh:mi:ss.mmm(24h)

-

126(***)

ISO8601

yyyy-mm-dd Thh:mm:ss.mmm(스페이스 없음)

-

130*

회교식****

dd mon yyyy hh:mi:ss:mmmAM

-

131*

회교식****

dd/mm/yy hh:mi:ss:mmmAM

             


문제 115. 오늘 부터 100달 뒤에 돌아오는 날짜가 어떻게 되는가?

SQL> SELECT ADD_MONTHS(SYSDATE,100)
            FROM dual;



문제 116. 오늘 부터 100달뒤에 돌아오는 날짜의 요일을 출력하시오! 

SQL> SELECT to_char(ADD_MONTHS(SYSDATE,100),'day')
            FROM dual;



문제 117. 이름과 커미션을 출력하는데 커미션이 NULL 인 사원들은 0으로 출력되게 하시오! 

SQL> SELECT ename,comm, NVL(comm,'0')
           FROM EMP;



문제 118. 이름, 커미션을 출력하는데 커미션이 null인 사원들은 no comm이란 글씨로 출력되게 하시오! 

SQL>  select ename, comm, nvl(TO_CHAR(comm), 'no comm')
             from emp;

주의 ! select ename, nvl(comm, 'no comm') 
           from emp;
         - 이 경우  comm은 number이고  치환하는 값이 string 이기 때문에 오류가 난다. 

Tip.
   nvl 은 data가 없는 상태, data를 문자 type을 바꾸어도 null 값은 data가 없다.
   그래서 변경, 치환된다.
   즉, 자신이 변환하고 싶은 모양으로 미리 데이터를 변환 시켜놓아야 한다. 

* MS_SQL은 Oracle 의 nvl 함수에 대응하는 isnull 이 있다. 
   - 그래서 Oracle의 nvl 함수와 동일한 효과를 MS_SQL에서 얻고 싶다면 isnull 함수를 사용해야 한다. 

   예시 ) SQL> select ename, isnull(cast(comm as varchar), 'no comm') 
                   from emp ;




문제 119. 이름, mgr을 출력하는데 mgr이 null인 사원들은 no manager란 글씨로 출력되게하시오!

 SQL> SELECT ename, NVL(TO_CHAR(mgr),'no manager')
             FROM EMP;  



문제 120. 이름, 월급, 커미션, 월급+커미션을 출력하시오! 

SQL> SELECT ename, sal, comm, sal+NVL(comm,0)
            FROM EMP;

SQL> SELECT ename, sal, comm, sal+NVL(comm,0),NVL2(comm,sal+comm,sal)
          FROM EMP;
 
* nvl2 (comm, sal+comm,sal) 
   1. 만약 comm 이 null 이 아니면 sal+comm 값을 출력하고 
   2. 만약 comm 이 null 이면 sal 값을 출력해라 
   (자바스크립트의 if/else 와 비슷한듯 하다.) 


주의!
 - nvl 에서 '0'이 숫자 이기 때문에 ' '이 필요 없다. 



문제 121. 이름, 직업, 보너스를 출력하는데 보너스가
             직업이 SALESMAN이면 3000으로 출력되게하고
             직업이 SALESMAN이 아니면 0으로 출력되게하시오 !

SQL> select ename, job, decode(job,'SALESMAN',3000,0) bonus
           from emp;


*decode/ null 값이 아닌 data가 있는 값 중 특정 data를 선택해야 할 때 

* MS_SQL은 decode를 지원하지 않는다. 
  - 그래서 Oracle의 decode 식을 MS_SQL에서 구현하기 위해서는 case 문을 사용해야 한다. (But. Oracle에서도 case 문을 지원한다.) 

   예시) select ename, job, (case job when 'SALESMAN' then 3000
                                                  else 0 end ) as bonus
      from emp ;



문제 122. 이름 직업, 보너스를 출력하는데 보너스가 직업이 SALESMAN이면 3000이 출력되게하고
               직업이 ANALYST면 2500이 출력되게하고
               나머지 직업은 0으로 출력되게하시오!

  SQL>  select ename, job, decode(job,'SALESMAN',3000,
                                               'ANALYST',2500,0) bonus
               from emp; 



문제 123. 이름, 나이, 등급을 출력하는데 
              나이가 30살 이상이면 A 등급
              나이가 27살 이상이면서 30미만이면서 B등급 
              나이가 24살 이상이면서 27살 미만이면 C등급
              나머지 나이는 D 등급으로 출력하시오! 

SQL> SELECT ename, age, case when age >=30 then 'A'
                                      when age >=27 then 'B'
                                      when age >=24 then 'C'
                                             else 'D' end as 등급 
           from emp2;

* decode는 등호비교만 가능하다. 부등호 비교를 하려면 case 를 사용해야 한다.
* case는 등호 비교, 부등호 비교 모두 가능하다.
* 부등호 사용시 자동으로 그 이전까지의 범위로 제한되는 듯 하다. 
* 범위는 between and를 가지고도 설정 가능하다. 



문제 124. 이름, 월급, 부서번호, 보너스를 출력하는데
               부서번호 보너스가 부서번호가 10번이면 자신의 월급의 40%를 보너스로 출력하고
               부서번호가 20번이면 자신의 월급의 20%를 보너스로 출력하고
               나머지 부서번호는 그냥 0으로 출력하시오!

 SQL> select ename, sal, empno, case when empno = 10 then sal*0.4
                                             when empno = 20 then sal*0.2
                                                         else 0 end as bonus
   from emp; 
         
주의! case는 한 번만 쓰자! 


* 점심시간 문제: ( 라인 검사) 

내가 태어난 생일의 요일을 출력하시오! 

SQL> SELECT ename,TO_CHAR(birth,'Day')
           FROM EMP2
           WHERE ename = '송수신';

* to_char 앞 부분에는 해당 날짜만 지정해주면 자동으로 요일을 찾아서 변환해준다. 



* 마지막 문제 :
 
   점심시간 면담순서 (연장자 부터) 일주일 목, 금
   
   월, 화,  수 

   이름, 나이, 분류 (A,B,C,D)
   A는 성별이 남자이고 서울에 거주하는 학생 
   B는 성별이 여자이고 서울에 거주 하는 학생 
   C는 성별이 남자이고 서울이 아닌 학생 
   D는 성별이 여자이고 서울이 아닌 학생 

    나이가 높은 순서대로 출력하시오! 

SQL> SELECT ename, age, CASE WHEN (gender='남자' AND address LIKE '%서울%') THEN 'A'
                                        WHEN (gender='여자' AND address LIKE '%서울%') THEN 'B'
                                        WHEN (gender='남자' AND address NOT LIKE '%서울%') THEN 'C'
                                        WHEN (gender='여자' AND address NOT LIKE '%서울%') Then 'D'
                                          END AS 분류
           FROM EMP2
           ORDER BY age DESC;

SQL> SELECT ename, age, CASE WHEN (gender='남자' AND address LIKE '%서울%') THEN 'A'
                                        WHEN (gender='여자' AND address LIKE '%서울%') THEN 'B'
                                        WHEN (gender='남자' AND address NOT LIKE '%서울%') THEN 'C'
                                          else 'D' END AS 분류
           FROM EMP2
           ORDER BY age DESC;

* 괄호는 안 넣어주어도 괜찮다. 




























  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 버전에 따라서 실행 통계 값이 부정확 할 수 있다. 




문제 59. 직업이 SALESMAN이 아닌 사원들의 이름과 월급과 직업과 입사일을 출력하는데,
              먼저 입사한 사원순으로 출력하시오  

SQL> 3 SELECT ename, sal, JOB, hiredate
           1 FROM EMP
            2 WHERE JOB != 'SALESMAN'
            4 ORDER BY hiredate ASC;

*keyword 앞에 번호들은 실행 순서 


문제 60. 20번 부서번호에서 근무하는 사원들의 이름과 월급과 연봉(sal*12)을 출력하는데
              연봉이 높은 사원부터 출력하시오 

SQL> SELECT ename, sal, sal*12 as "연봉"
           FROM EMP
           WHERE deptno = 20
           ORDER BY "연봉" DESC;



문제 61. 우리반 테이블에서 이름과 나이를 출력하는데 나이가 높은 사람부터 출력하시오!

SQL >SELECT ename, age
            FROM emp2
            ORDER BY age DESC;



문제 62. 우리반 테이블에서 여학생의 이름과 주소와 나이를 출력하는데
                 나이가 높은 순서대로 출력하시오! 

SQL>SELECT ename, address, age
          FROM emp2
          WHERE gender = '여자'
          ORDER BY age DESC;



문제 63. 우리반 테이블에서 성씨가 김씨인 학생의 이름과 나이를 출력하시오! 

SQL>SELECT ename, age
          FROM emp2
          WHERE ename LIKE '김%';



문제 64. 나이가 30대인 학생들의 이름과 성별과 나이를 출력하시오!

SQL>SELECT ename, gender, age
           FROM emp2
           WHERE age BETWEEN 30 AND 39;



문제 65. 서울에 사는 학생들에 이름과 주소를 출력하시오!   

SQL>SELECT ename, address
           FROM emp2
           WHERE address LIKE '%서울%';



문제 66. 전공이null인 학생들의 이름과 전공을 출력하시오!

SQL> SELECT ename, major
            FROM EMP2
            WHERE major is NULL;

주의! null 같은 '='가 아닌 is 를 사용



문제 67. 전공을 출력하는데 중복제거해서 출력하시오!

SQL> SELECT DISTINCT major
            FROM EMP2;



문제 68. 전공이 컴퓨터 공학이 아닌 학생들의 이름과 전공을 출력하시오!

SQL > SELECT ename, major
            FROM EMP2
            WHERE major <> '컴퓨터공학'; 

* null 이 아닌 이상  'is'를 쓰지 않는다. 



문제 69. 주소가 서울이 아닌 학생들의 이름과 주소를 출력하시오! 

SQL > SELECT ename, address
            FROM EMP2
            WHERE address NOT LIKE '%서울%';

* 'not like' 도 사용가능



문제 70. 학원까지 오는 시간이 가장 적게 걸리는 학생부터 이름과 주소와 시간을 출력하시오! 

SQL > SELECT ename, address, distance
            FROM EMP2
            ORDER BY distance ASC;



문제 71.  전공이 컴퓨터 공학이고 성별이 여학생인 학생들의 이름과 전공과 주소를 출력하는데,
               집과의 걸리가 먼 학생순으로 출력하시오 

SELECT ename, major, address
  FROM EMP2
  WHERE major ='컴퓨터공학' AND gender ='여자'
  ORDER BY distance DESC;



문제 72. 같은 라인에 있는 학생들의 결과가 아래와 같이 출력되게하시오! 

            곽민영 학생의 나이는 34이고 전공이 컴퓨터 공학이며 집에서 학원으로 오는 시간은 3분 입니다. 

SELECT ename||' 학생의 나이는'||age||'이고 전공이 '||major||'이며 집에서 학원으로 오는 시간은 '||distance||'분 입니다.'
  FROM EMP2
  WHERE empno BETWEEN 16 AND 19;

* MS_SQL 구현시 

  SELECT concat( ename,' 학생의 나이는',age,'이고 전공이 ',major,'이며 집에서 학원으로 오는 시간은 ',distance,'분 입니다.')
  FROM EMP2
  WHERE empno BETWEEN 16 AND 19;



3장 함수 



- 함수를 왜 배워야하는가? 
  아래와 같은 정보을 알기 위하여 배워야 한다. 

ex)
우리반 테이블에서 전공, 전공별 인원수를 출력하시오!

우리반 테이블에세 가장 나이가 어린 학생의 이름을 출력하시오!

메일 도메인, 메일 도메인별 건수를 출력하시오! 



- 함수의 종류 2가지? 


1. 단일행 함수
   - 문자
   - 숫자
   - 날짜
   - 변환
   - 일반 

1) 문자 함수 
1. 대소문자 변환 함수 
     - lower : 소문자로 출력하는 함수
     - upper : 대문자로 출력하는 함수
     - initcap: 첫번째 철자는 대문자 나머지는 소문자로 출력하는 함수 

2) 문자 조작 함수 
      - concat : 두개의 컬럼의 값을 연결하는 함수
      - substr : 특정 철자를 잘라내는 함수
      - length : 철자의 길이를 출력하는 함수 
      - instr : 특정 철자의 위치를 출력하는 함수
      - lpad : 왼쪽에 특정값을 채워넣는 함수 
      - rpad : 오른쪽에 특정값을 채워넣는 함수 
      - trim : 특정 철자나 값을 잘라내는 함수 
      - replace : 특정 철자로 변경하는 함수 

2. 복수행 함수
   - max
   - min
   - count
   - avg
   - sum



문제 73.  아래와 같이 결과를 출력하시오! 

SQL> select upper(ename), lower(ename), initcap(ename)
           from emp;

* MS_SQL 로 작성시 
  - MS_SQL은 inticap 함수가 없어서 Oracle의  inticap 함수 처럼 나타내기 위해서는 아래와 같이 사용해야 한다. 

    SQL> select upper(ename), lower(ename), concat(upper(left(ename,1)),lower(substring(ename,2,10)))
           from emp;



문제 74. 이름이 smith인 사원의 이름과 월급을 출력하시오! 
              아래 ? 부분을 채워 넣으시오!

     where ? = 'smith'

SQL> SELECT ename, sal
           FROM EMP
           WHERE LOWER(ename) = 'smith';

* 자료가 대문자 인지 소문자 인지 확실히 모를 때 유용하게 사용할 수 있다.



문제 75. 이름과 월급을 붙여서 출력하시오! 
 
 SQL> select concat(ename, sal)
            from emp;
 
* concat은 2개의 컬럼만 붙일 수 있다. 

* MS_SQL 에서 concat 함수 
  - MS_SQL에서는 concat 안에 여러개의 컬럼은 붙일 수 있다. 



문제 76. 우리반 테이블에서 이름과 나이를 붙여서 출력하시오!

 SQL> SELECT CONCAT(ename, age)
            FROM EMP2;



문제 77. 이름을 출력하는데 이름의 첫번째 철자만 출력하시오! 

SQL> select ename, substr(ename,1,1)
           from emp;

* substr(컬럼명, 숫자1, 숫자2) 
- 숫자1 : 시작점을 의미한다. 
- 숫자 2: 시작점 부터 몇개를 출력할 것인지 입력한다. 

* MS_SQL 에서 해당 철자만 사용하기 
  - Oracle substr 함수에 해당 하는 MS_SQL 함수는 substring 이다. 

  SQL> select ename, substring(ename,1,1)
           from emp;



문제 78. 이름의 세번째 철자만 출력하는데 소문자로 출력하시오! 

SQL>SELECT LOWER(substr(ename,3,1))
           FROM EMP;

* MS_SQL 에서 출력 방법 
SQL> SELECT LOWER(substring(ename,3,1))
          FROM EMP;



문제 79. 아래의 쿼리의 결과를 initcap 사용하지 말고,
              upper, lower, substr || 를 사용해서 출력하시오!

예시) select initcap(ename)
           from emp;

SQL> SELECT UPPER(substr(ename,1,1))||LOWER(substr(ename,2,8))
            FROM EMP;



문제 80. 우리반 테이블에서 성씨가 김씨, 이씨, 박씨인 학생들의 이름과 주소를 출력하시오!
              (like 사용하지 말고)

SQL> SELECT ename, address
           FROM EMP2
           WHERE substr(ename,1,1) IN ('김','이','박');

* MS_SQL에서 출력 방법 

SQL> SELECT ename, address
           FROM EMP2
           WHERE substring(ename,1,1) IN ('김','이','박');



문제 81. 이름과 이름의 철자의 갯수를 출력하시오 !

 SQL> select ename, length(ename)
            from emp;

* MS_SQL 에서 철자의 갯수 출력 방법 
- Oracle length에 해당하는 MS_SQL 의 함수는 len 이다 

SQL> select ename, len(ename)
         from emp;



문제 82. 이름, 이메일, 이메일의 철자의 갯수를 출력하는데, 
             이메일의 철자의 갯수가 가장 많은 학생부터 출력하시오! 

SQL>SELECT ename,email, length(email)
          FROM EMP2
          ORDER BY LENGTH(email) DESC;

Tip.
  select 절에서 length에 alias 를 주고 order by에 alias를 활용하여도 됨

* MS_SQL에서 위와 같은 사용 법
SQL>SELECT ename,email, len(email)
          FROM EMP2
          ORDER BY LEN(email) DESC;



문제 83. 이름 이메일의 도메인만 출력하시오!

SQL> SELECT substr(email,instr(email,'@')+1,(instr(email,'.')-instr(email,'@')-1))
           FROM EMP2;

* 다른 방법들도 있다. length를 사용해서 뒤에서 부터 빼주는 방법도 있다. 
   
   SQL> SELECT substr(email,instr(email,'@')+1, length(email)-instr(email,'@')-4 )
              FROM EMP2;

* MS_SQL 로 위의 쿼리 변환 시 
 - 함수 변화가 필요하다.  
 SQL> SELECT substring(email,patindex('%@%',email)+1,(patindex('%.%',email)-patindex('%@%',email)-1))
          FROM EMP2 ;



문제 84. 이름을 출력하고 그 옆에 이름에 철자 A가 몇번째 있는지 출력하시오! 

 SQL> select ename, instr(ename, 'A')
             from emp;

*instr/ 해당 문자가 몇 번째에 위치하는지 알수 있게 해주는 keyword

* MS_SQL에서 instr 대체 사용법 
   - MS_SQL에서 instr을 대체하는 함수는 patindex 이다 
 SQL> select ename, patindex('%A%',ename)
             from emp;



문제 85. 우리반 테이블에서 이메일을 출력하고
              그 옆에 이메일의 @ 몇번째 자리에 있는지 출력하시오!

SQR> SELECT email, instr(email,'@')
            FROM EMP2; 



문제 86. 이메일을 출력하는데 @ 다음의 철자부터 출력하시오! 

SQR> SELECT substr(email,instr(email,'@')+1)
            FROM EMP2;

*substr에서 두번째 자리에 아무것도 쓰지 않으면 끝까지 출력 된다.

* MS_SQL에서 substring 사용법 
   - MS_SQL에서 substring은 무조건 3개의 인자가 있어야지 쿼리가 실행된다. 
   - 아래의 쿼리는 필요 인자 부족으로 error가 발생한다. 

SQL> SELECT substring(email,patindex('%@%',email)+1)
         FROM EMP;



문제 87. 이름과 월급을 출력하는데 월급을 10자리로 출력하고, 
             월급이 출력되는 나머지 자리에*를 출력하시오! 

  select ename, lpad(sal, 10, '*')
     from emp;

*lpad/rpad
  - 자리 개수를 선정하고 나머지는 다른 것으로 채우겠다. 
  - ex) lpad(컬럼명, 자리개수, 채울 내용)

* MS_SQL 에서 lpad 대체법 
 - MS_SQL은 lpad가 사용되지 않는다. 그래서 replicate를 사용해서 쿼리를 작성해야 한다. 
   또한, 숫자와 같은 경우 형변환이 일어나서 기존에 숫자 값도 형변환을 해줘야 한다. 

SQL> select replicate('*',10-len(sal))+cast(sal as varchar) 
         from emp ;



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

SQL> insert into emp(empno, ename, sal)
           values(1235,'  JACK  ', 3600);

         select ename, sal
           from emp
           where trim(ename) = 'JACK';

*trim 
 - 앞, 뒤의 공백을 지워준다. (단, 중간에 공백은 지워주지 못한다.)

* MS_SQL 의 Trim 함수 대체 방법 
 - MS_SQL은 trim 함수를 사용할 수 없어서 ltrim 과 rtrim을 중첩 사용하여 Oracle의 trim 함수와 같은 효과를 낼 수 있다. 

SQL> select ename, sal
         from emp
       where rtrim(ltrim(ename)) = 'JACK';



문제 89. 이름과 전공을 출력하는데 전공을 출력할때에 과 가 안나오게 하시오! 

SQL> SELECT ename, TRIM('과' FROM major)
           FROM EMP2;

* MS_SQL 에서 Trim 함수 대체 방법 2 
  - MS_SQL 에서는 Trim 함수를 사용할 수 없어서 replace를 활용하여서 쿼리를 작성해야 한다. 

SQL> SELECT ename, replace(substring(major,1,1),'과','') + substring(major,2,(len(major)-2)) + replace(substring(mjor,len(ename),1),'과','')
          FROM EMP2 ;



문제 90. 이름과 월급을 출력하는데 월급을 출력할때에 0을 *로 출력하시오! 

SQL> select ename, replace(sal, 0, '*')
          from emp;

* replace
   - 대체하는 keyword
   - replace(컬럼명, 대체하고 싶은 문자 or 숫자, 대체할 숫자 or 문자)
   - regexp_replace(sal, '[0-2]', '*')를 사용해서 좀 더 확장할 수 있다. 



문제 91. 우리반 테이블에서 20대인 학생들 중에 naver 메일을 사용하는 학생들의 이름과 주소와 나이와 메일주소를 출력하는데, 나이가 높은순서대로 출력을 하고 그리고 컬럼명이 아래와 같이 출력되게 하시오! 

      학생이름, 사는 곳, 메일주소, 나이 

SQL> SELECT ename AS "학생이름", address AS "사는곳", email AS "메일주소", age AS "나이"
            FROM EMP2
            WHERE age BETWEEN 20 AND 29
               AND email LIKE '%naver%'
           ORDER BY age DESC;

 





  5) password file
    - 특별한 권한을 가진 유저를 인정해주기 위한 파일 (ocm)
      (db를 올리고 내릴 수 있는 권한)

   * 패스워드 파일의 위치 확인 방법
    $cd $ORACLE_HOME/dbs
    $ls -l orapw*

  * 패스워드 파일에 등록된 유저를 확인하는 방법?

   SQL> select * 
            from v$pwfile_users;

    * 패스워드에 등록하기 유저 
 
    SQL> grant sysdba to scott;
        ( But. scott으로만 접속하면 db를 내릴 수 없다. ) 
   (SQL> connect scott/oracle_4U as sysdba 이렇게 접속해야 사용할 수 있다.)
                               (sysdba의 비밀번호)               

   * 패스워드 파일을 이용하여 remote로 연결시 sysdba 권한으로 연결하는 방법 
    sqlplus sys/oracle_4U@orcl as sysdba 
    ( @orcl을 붙여준 이유는 리스너를 타고 접속하겠다는 뜻, 즉 다른 말로 말하면 원격에서 내 db로 sys 유저로 접속할 수 있게 하겠다.)  


  6) parameter file
   - 인스턴스의 구조정보를 담고 있는 파일 

   * 파라미터 파일의 위치 확인 
  
    SQL> show parameter spfile

   * 파라미터 파일의 내용을 확인하는 방법 

    SQL> create pfile='/home/oracle/initorcl.ora'
              from sqfile='+DATA/orcl/spfileorcl.ora';

    $cd /home/oracle
    $vi initorcl.ora


   7) database configuration
       가. non clustered system
       나. clustered system (=RAC)
            real Application Cluster

    * 노드 (컴퓨터 묶음의 단위) / 기본적으로는 4노드로 주로 사용/ 이론적으로는 100대까지 묶을 수 있음
    * 24시간 중단없는 서비스를 지원받을 수 있다는 것이 장점

   8) connection 과 session의 차이

     * connection 이란 
        - user process가 instance와 통신할 수 있는 상태가 되었다. 
           client -----------------> server
        user process             server process
            SQL ------------------> DATA
                 <-----------------

     * instance란
       - 오라클의 메모리 

     * session 이란 
       - instance에 connection해서 connection을 끊을때까지의 한 단위가 session 

     9) 오라클 메모리 구조
        * 오라클 메모리 2가지는 무엇인가?

        가. SGA (System(or shared) Global Area)
            - 여러 서버프로세서들과 백그라운드 프로세서들이 공유해서 사용하는 메모리 영역 
              (현재 sga 영역의 사이즈
                SQL> select name, value
                            from v$sga;)            
   
            (1) SGA 영역 중 shared pool의 역활
                - parsing을 최소화하기 위한 오라클 메모리 영역
                - parsing은 SQL을 기계어로 변경하는 과정
   
               - select 문 처리 과정 
 
                ① parsing ---> SQL ---> 기계어
                        - 문법 검사 : SQL 문법에 이상이 없는지 확인 
                        - 의미 검사 : 1) object 확인 : emp 테이블의 존재 
                                       2) 권한 확인 : emp 테이블을 엑세스 할 수 있는 권한 체크 

                ② execute ---> Data 를 검색 
                ③ fetch --> 결과를 전달 
                  
               * shared pool 에 parsing 결과를 올려 놓는다. 
                  (-  결과물 3가지 
                       1. SQL 문장 
                       2. 실행 계획
                       3. 실행 코드 : SQL 문장을 c언어를 통해 실행코드로 변환시킨다.
                  (- 3가지 결과물을 동일한 결과가 입력될 시 공유해서 사용하기 위해서 shared pool에 위의 3가지 결과를 올린다.)  

        2) PGA (Program Global Area)
           - 서버 프로세서들의 개별 메모리 영역이고 이 영역에서 data 정렬작업이 발생한다.  


문제7. scott 유저를 패스워드 파일 등록리스트에서 제외하시오!

SQL> revoke sysdba from scott;


문제 8. sysdba로 접속하는 유저의 패스워드를 oracle로 지정하고 패스워드의 대소문자를 반드시 구분되게 하시오! (m)

- 패스워드 파일을 rm으로 날린다. 
   ( rm orapw*)

- orapwd file=orapworcl password=oracle ignorecase=N (entries=5 ; 해당 비밀번호를 받을 사람을 5명으로 제한)
                    (orapw + instance 이름이 규칙)

* orapwd  : 오라클 패스워드 파일을 만드는 유틸리티 
* SQL> select name from v$database; ( instance 이름을 확인하는 방법)
    (인스턴스 (오라클의 메모리) 이름 확인) 


문제 9. 오라클에 sysdba 권한을 가진 유저로 오라클 db에 접속할 때 패스워드를  oracle_4U로 지정되게하고 대소문자를 구분하지 않아도 접속되게해보시오!패스워드 파일에 등록될 유저를 3명으로 제한하시오! 

$ rm orapw* 

$ orapwd file=orapworcl password=oracle_4U ignorecase=Y entries=3


문제 10. scott 과 같은 일반유저로 접속시 대소문자 구분을 안하겠금 설정하려면 어떻게 해야하는가?

SQL> show parameter sec_case_sensitive_logon

NAME                                         TYPE        VALUE
------------------------------------ ----------- ------------------------------
sec_case_sensitive_logon             boolean     TRUE

(value 값을 false로 바꿔야 한다.) 

SQL> alter system set sec_case_sensitive_logon=false;
          

문제 11. 오라클의 접속되어져있는 세션들을 확인하시오! 

 SQL> select username, status
           from v$session;

 * 실행시 보이지 않는 user은 background 프로세서이다. 


문제 12. scott으로 접속한 세션을 


SQL> select  spid
        from  v$process
       where  addr = (
                          select paddr
                          from v$session
                          where sid = (  select sid
                                                from v$mystat
                                                where rownum=1) );

* spid로 확인한 후 수행한 것들 
ps -ef |grep 15969
top -p 15969
kill -9 15969

문제 13. Parsing을 과다하게 일으키는 프로그램을 돌리고 top으로 cpu 사용율을 확인하시오! 

<하드 parsing 문>
SQL> declare

 type rc is ref cursor;
 l_rc rc;
 l_dummy all_objects.object_name%type;
 l_start number default dbms_utility.get_time;
 begin
 for i in 1 .. 50000
 loop
 open l_rc for
 'select object_name from all_objects where object_id = ' || i;
 fetch l_rc into l_dummy;
 close l_rc;
 end loop;
  dbms_output.put_line ( round( (dbms_utility.get_time - l_start)/100, 2) || 'seconds');
  end;
  /

<top 화면>
Mem:   2075456k total,  2014260k used,    61196k free,   179876k buffers
Swap:  4128760k total,   148480k used,  3980280k free,  1404340k cached

  PID   USER      PR  NI  VIRT  RES  SHR  S %CPU %MEM    TIME+  COMMAND                                        
16198 oracle    25   0  634m 137m 119m R 99.6    6.8       4:09.27  oracle                                         
 4887  oracle    15   0  410m  23m  11m  S  0.3    1.2      83:02.18  oraagent.bin                                   
14408 oracle    15   0 73116  13m  9332  R  0.3    0.7      0:11.75    gnome-terminal                                 
16214 oracle    15   0  2336 1104    792   R  0.3    0.1      0:00.17    top                                            
    1     root      15   0  2072  624     532   S  0.0    0.0      0:06.29    init                                           
    2     root      RT  -5    0      0        0     S  0.0    0.0       0:01.33    migra

<이 부분은 추후 스크린샷으로 대체>


문제 14. emp 테이블에 db 에 존재하는지 확인하시오! 

SQL>  select table_name
       2  from dba_tables
      3 where table_name = 'EMP'

* user < all < dba 순으로 많은 정보를 가지고 있다. 

문제 15. scott 유저에서 아래의 SQL을 실행하고 shared pool에 올라가져 있는지 확인하시오! 

-scott 유저

 SQL>  select empno, ename
       2   from emp
        3 where empno =7788


     EMPNO ENAME
---------- ----------
      7788 SCOTT


- sys 유저

SQL> select sql_text, executions
  2   from v$sql
  3  where sql_text like 'select empno%';

SQL_TEXT
--------------------------------------------------------------------------------
EXECUTIONS
----------
select empno, ename  from emp where empno =7788
         2


* 똑같은 문장이 들어와야지 동일한 쿼리 결과를 이용할 수 있다. 
* 똑같은 문장 ; 1. 대소문자가 동일, 2. 공백도 동일, 3. 소유자도 동일


문제 16. 아래의 문장을 이번에는 대문자로 실행하고 shared pool에 새롭게 파싱해서 올렸는지 아니면 기존의 parsing 정보를 활용했는지 확인하시오! 

SQL>  select EMPNO, ENAME  
          from EMP 
       where EMPNO =7788


SQL> select sql_text, executions
    2   from v$sql
    3  where sql_text like 'SELECT EMPNO%'or
    4 sql_text like 'select empno%' ;

SQL_TEXT
--------------------------------------------------------------------------------
EXECUTIONS
----------
select empno, ename  from emp where empno =7788
         2

SELECT EMPNO, ENAME FROM EMP WHERE EMPNO=7788
         1


문제 17. 아래의 SQL을 수행하고 다시 파싱했는지 확인하시오!

SQL>  select empno,             ename  from emp where empno =7788  
         (공백이 다르게 들어감) 

select sql_text, executions
  2   from v$sql
  3  where sql_text like '%EMPNO%'

SQL> ed
Wrote file afiedt.buf

  1  select sql_text, executions
  2   from v$sql
  3   where sql_text like '%empno%' /

SQL_TEXT
--------------------------------------------------------------------------------
EXECUTIONS
----------
select sql_text, executions  from v$sql where sql_text like 'select empno%'
         2

select sql_text  from v$sql where sql_text like 'SELECT EMPNO%'or       sql_text
like 'select empno%'
         1

select empno,             ename  from emp where empno =7788
         1

SQL_TEXT
--------------------------------------------------------------------------------
EXECUTIONS
----------

select empno, ename  from emp where empno =7788
         2

select sql_text, executions  from v$sql  where sql_text like '%empno%'
         1

select sql_text, executions  from v$sql  where sql_text like 'empno%'
         1

SQL_TEXT
--------------------------------------------------------------------------------
EXECUTIONS
----------

select sql_text  from v$sql  where sql_text like 'select empno%'
         4

select sql_text, executions  from v$sql where sql_text like 'SELECT EMPNO%'or
    sql_text like 'select empno%'
         1


8 rows selected.


문제 18. 아래의 SQL을 수행하는데 smith 유저를 생성해서 수행해보시오

- scott 
  select empno, ename from where empno = 7788;

- smith 
  select empno, ename from emp where empno=7788;

1. smith 유저 생성 
2. smith 유저로 접속해서
3. demobld.sql을 돌린다. 


* 고민해야 할 문제 (이부분은  Linux 문제) :
  asm 를 입력하면 asmcmd> 로 바로 들어갈 수 있도록 하시오

$ export ORACLE_SID=+ASM
$ export ORACLE_HOME=/u01/app/oracle/product/11.2.0/grid

$asmcmd
asmcmd>

or 

$.oraenv
$   ? +ASM
$ asmcmd



* export ORACLE_HOME=/u01/app/oracle/product/11.2.0/grid
   - application을 작동 시킬때는 해당 application이 실행 될 수 있도록 home 값을 지정해주어야 한다. 



문제 26. 커미션이 null 이 아닌 사원들의 이름과 월급과 커미션을 출력하시오!
 
SQL>  select ename, sal, comm
          from emp
       where comm is not null;



문제 27. 직업이 SALESMAN이고 월급이 1000 이상인 사원들의 이름과 월급과 직업을 출력하시오!

 SQL> select ename, sal, job
           from emp 
           where job = 'SALESMAN' and sal >= 1000

Tip.
  컬럼 별칭을 정할 수 있다.
  ex) sal as 월급

주위! 아래와 같은 경우는  오라클 실행 순서에 의해서 에러가 남

    SQL>  select ename, sal as 월급, job
              from emp 
            where job = 'SALESMAN' and 월급 >= 1000
 


문제 28. EMP 테이블의 모든 컬럼을 출력하시오! 

SQL > select *
          from emp;


문제 29. dept 테이블의 모든 컬럼을 출력하시오! 

SQL > select * 
          from dept;

'*'의 의미 
 - 'asterisk'가 불리며 select 절에 사용 시 해당 테이블의 모든 컬럼을 출력한다.  


문제 30. SQL 작성 규칙이 어떻게 되는가?
    
   1. SQL은 대소문자를 구분하지 않는다. (주로 keyword를 대문자로 구분)
   2. 한줄 또는 여러줄에 입력할 수 있다. (아래로 작성할 수 있다는 것이 중요)
   3. 키워드는 약어로 표시하거나 여러줄에 나뉠 수 없다. (keyword는 select, from 과 같은 것들)
   4. 절은 별도의 절에 입력해야 하는 것이 좋다 
   5. 가독성을 높이기 위해서 들여쓰기를 사용해라 
 

문제 31. 아래의 쿼리의 컬럼명은 대문자로 출력되는가 소문자로 출력되는가?

SQL > select ename, sal, deptno
            from emp; 

answer) 대문자로 출력 됨 


문제 32. 위의 컬럼명을 소문자로 출력되게하시오! 

SQL > select ename, sal, deptno
            from emp; 

answer > select ename as "ename", sal as "sal", deptno
            from emp; 

* " "(더블쿼테이션 마크)를 사용해야 하는 때는

  1. 컬럼별칭 사용시 대소문자를 구분하고 싶을때 
  2. 컬럼별칭 사용시 공백문자나 특수문자를 출력하고 싶을때  


문제 33. 사원이름과 월급을 출력하는데 컬럼 별칭을 이용해서 아래와 같이 결과가 출력되게 하시오! 

컬럼명  ---> Employee, Salary

SQL > select ename as "Employee", sal as "Salary"
            from emp;

* 여기서 'as'는 생략 가능


문제 34. 연봉이 36000이상인 이름과 연봉(sal*12)을 출력하는데 컬럼명이 한글로 이름, 연봉으로 출력되게 하시오  !

SQL > select ename as 이름, sal*12  as 연봉 
           from emp
           where sal*12 >= 36000;

주의! 연산 순서도 중요하다 
sal*12+300 은 sal+300*12와 다르다.


문제 35. 이름과 직업을 출력하는데 아래와 같이 컬럼명을 사원의 이름, 사원의 직업이라고 출력하시오!
   
            - 사원의 이름, 사원의 직업 

SQL > select ename as "사원의 이름", job as "사원의 직업"
            from emp;


문제 36. 직업을 출력하는데 중복을 제거해서 출력하시오!

SQL > select distinct job
            from emp;


문제 37. 부서번호를 출력하는데 중복제거해서 출력하시오!

SQL > select distinct deptno
            from emp;


문제 38. 이름과 월급을 연결해서 출력하시오!

SQL >  select ename ||'의 월급은' ||sal
             from emp; 


*연결 연산자  :  '||'(수직바 2개)

* MS SQL 은 concat을 사용해서 연결한다 
  SQL> select concat (ename, '의 월급은', sal) 
            from emp ;

문제 39. 아래와 같이 결과를 출력하시오!

    KING 의 직업은 PRESIDENT 입니다. 

SQL > select ename||' 의 직업은 '||job||' 입니다.'
           from emp;


문제 40. 1-23쪽 (대체 인용 연산자)을 참고해서 문제를 해결하시오!

             아래와 같이 결과를 출력하시오 !

             SMITH의 부서번호는 '20'번 이고 직업은 CLERK 입니다. 

 SQL > select ename||'의'|| q'['부서번호'는]'||deptno||'번 이고 직업은'||job||'입니다'
           from emp;

* MS SQL 에서 싱글쿼테이셥 select 방법 
 SQL> select concat (ename, '''의 월급은''', sal) 
          from emp ;


문제41. DEPT 테이블의 구조를 확인하시오!

 SQL > desc dept 

* MS SQL 에서 테이블 컬럼 구조 확인 

 SQL> sp_columns dept ;




2장. data 검색 제한과 정렬 



1. where 절
2. order by 절

문제 42. 20번 부서번호에서 근무하는 사원들의 이름과 월급과 부서번호를 출력하시오!

SQL > select ename, sal, deptno
          from emp
          where deptno = 20;


문제 43. 부서번호가 20, 30번에서 근무하는 사원들의 이름과 부서번호를 출력하시오! 

SQL > select ename, deptno
          from emp
          where deptno in (20,30);


문제 44. 80년 12월 11일에 입사한 사원들의 이름과 입사일을 출력하시오! 

SQL > select ename, hiredate
          from emp
          where hiredate = '81/12/11'; (or '81-12-11' , '81.12.11')
                                    '년/달/일'

*양쪽에 싱글 쿼테이션 마크를 사용해야 하느 경우는?
 1) 문자 
 2) 날짜 

 * 날짜를 검색하기 전에 반드시 확인해봐야할 사항 
       - 현재 재가 접속한 세션의 날짜 형식을 확인해야한다. 
*NLS_DATE_FORMAT      RR/MM/DD
                                       년/월/일
         
Tip. 날짜 형식을 확인 하는 방법
          SQL> select* 
                     from nls_session_parameters;
                     (nls/ National Language Support) 
         
 Tip. 컬럼 출력 포맷 변경 방법
        column parameter(column 명) format a20(a숫자) 
        keyword 'column'은 SQL*PLUS  명령어

* MS SQL 에서 날짜 형식 확인 방법 
   SQL> select GETUTCDATE() ;


문제 45. 현재 세션의 날짜 형식중 년도를 4자리로 표시되게 하시오!

SQL> alter session set nls_date_format='RRRR/MM/DD';

Tip.
    접속하고 나서 접속을 끊을때까지가 하나의 'session'
    so. 그래서 현재 상태는 하나의 세션에서만 유요함 (=종료하기 전까지만) 

*MS SQL도 날짜 형식 변환은 가능하지만 현재까지의 자료로는 시스템의 날짜 형식의 변환은 안되는 것으로 보인다. 
   - 추후 더 알아봐야 함 


문제 46. 1983년 1월 15일에 입사한 사원들의 이름과 입사일을 출력하시오!

SQL> select ename, hiredate
          from emp
          where hiredate = '1983/01/15'

* MS SQL 에서는 날짜 변환의 개념이 오라클과 약간 다르다. 

1) 아래의 명령을 사용하면 조회시 사용하고자 하는 날짜 형식을 바꿀 수 있다.  
  SQL> select CONVERT(char(10),current_timestamp,111) ;

  SQL> select ename, hiredate
           from emp 
         where hiredate = '1981/11/17' ;

2) 그러나, select 시 나오는 날짜 형식은 변하지 않았다. 
   아래의 문장 출력 시 기존에 입력된 날짜 형식으로 출력되어서 나온다. 

  SQL> select ename, hiredate
           from emp 
         where hiredate = '1981/11/17' ;

3) 또한, 날짜 형식을 바꿔도 시스템 현재의 날짜 형식은 바뀌지 않는다. 
 
  SQL> select GETUTCDATE() ;


문제 47. 1981년도에 입사한 사원들의 이름과 입사일을 출력하시오 !

SQL> select ename, hiredate
         from emp
         where hiredate like '1981%' or('1981/%/%'로 하면 날짜까지 가능함)

SQL> select ename, hiredate
         from emp
         where hiredate between '1981/01/01' and '1981/12/31';
         (이것이 좋은, 효율적인 SQL 이다.)

* '=' 은 완벽하게 같을 때만 사용한다.


문제 48. 이름에 끝글자가 T로 끝나는 사원들의 이름을 출력하시오!

SQL> select ename
         from emp
         where ename like '%T'


문제 49. 이름의 두번째 철자가 %인 사원의 이름을 출력하시오!

SQL> select ename
         from emp
         where ename like '_m%%'  escape 'm'
        

문제 50. 아래의 data를 입력하고 이름의 두번째 철자와 세번째 철자가 %인 사원의 이름을 출력하시오!

             insert into emp(empno, ename, sal)
               values(3456,'A%%B',4500);

           commit;

SQL > select ename
            from emp
            where ename like '_m%m%%' escape 'm'


문제 51. 직업이 SALESMAN이고 30번 부서번호에서 근무하는사원들 중 월급이 1000아상인 사원들의 이름과 월급과 직업과 부서번호를 출력하시오! 

SQL > select ename, sal, job, deptno
           from emp
           where job ='SALESMAN' and deptno = 30 and sal >= 1000;

*우선 순위 규칙이 있다. 
  (참고 and 는 or 보다 우선작동한다, or를 먼저 우선하고 싶으면 괄호를 사용한다.)


문제 52.  이름과 월급을 출력하는데 월급이 낮은 사원부터 높은 사원순으로 출력하시오! 

SQL > select ename, sal
           from emp
           order by sal asc;

*order by 절 사용법 
  - order by 절은 코딩 & 실행 순서 모두 맨 마지막에 실행된다. 

Tip. asc는 ascend의 약자 


문제 53. 이름과 월급을 출력하는데 월급이 높은 사원부터 출력하시오!

SQL > select ename, sal
           from emp
           order by sal desc

Tip. desc는 descend의 약자 


문제 54. 직업이 SALESMAN인 사원들의 이름과 직업과 월급을 출력하는데 월급이 높은 사원부터 출력하시오! 

 SQL>  SELECT ename, JOB, sal
              FROM EMP
              WHERE job='SALESMAN'
             ORDER BY sal DESC;

(이 문제 부터 SQL Gate 2010 사용)


문제 55. 3번 부서번호인 사원들의 이름과 입사일을 출력하는데 최근에 입사한 사원부터 출력하시오! 

 SQL> SELECT ename, hiredate
            FROM EMP
            WHERE deptno = 30
            ORDER BY hiredate DESC;


문제 56. 자살데이터를 입력받기 위한 테이블을 생성하시오!

SQL > create table sucide
            (country varchar2(40),
              male number(10),
              female number(10),
              average number(10) ); 

* create talbe '테이블명'


문제 57. 나라이름과 남자 자살 데이터를 출력하는데 남자 자살 데이터가 많은 순서대로 출력하시오! 

SQL > SELECT country, male
             FROM sucide
             ORDER BY male DESC nulls last;

Tip. 
nulls last를 쓰면 null data를 가장 뒤로 보낸다. 


문제 58. 우리나라의 평균 자살수가 어떻게 되는지 출력하시오! 

SQL > SELECT average
            FROM sucide
            WHERE country ='South Korea6 ';
 


* 추가할 내용 _ SQL gate에서 데이터 추가하는 법*

1. '도구' 옵션 클릭 
2. '데이터 가져오기' 클릭
3. 테이블 찾기 (미리 생성한)
4. 데이터 종류 선택
5. 데이터를 찾아서 선택
6. 필드 구분 기호 (쉼표)
7. 첫열 '2'로 변경  













가끔 프랑스 영화를 본다. 

내가 본 프랑스 영화들을 솔직히 별로 재미 없었지만, 

보게 된 이유는 소재의 참신함 때문이었다. 

이번에 보게 된 '이웃집에 신이 산다' 또한 소재의 참신함으로 보게 되었다. 

이웃집에 신이 산다는 소재 (솔직히 말하면 이웃집은 아니지만) 즉 우리와 비슷한 일상으로 신이 산다는 발상이 참신하였다. 

그 발상의 참신함에 다시 한 번 믿어보기로 했고 보게 되었다. 

그러나 역시나 아직 나의 식견과 생각의 깊이가 부족했던 것인지 무슨 말을 하고 싶었던 것인지 알지 못하였다. 

뭐 억지로 감정과 감동을 지어내자면 못하는 것도 아니지만 실제로 느꼈고 이해하지 못했다. 

그래도 한 가지 생각해보았던 것은 있다. 

만약 영화에서 처럼 나에게 남은 수명을 알게 된다면 그리고 그 남은 수명이 얼마 남지 않았다면 나는 무엇이 하고 싶을까?

이 영화에서는 가끔 얼마 남지 않은 수명 또는 길게 남은 수명으로 말도 안되는 행동들을 하지만 과연 나의 경우는 무엇일까?

어쩌면 많은 사람들이 직면하고 있는 문제들이 죽음을 앞두고는 의미 없는 일을 될지도 모른다. 

특히 자신이 하고 싶은 일을 하지 못하고 미래에 생활에 대한 두려움에 원치 않는 삶을 사는 사람들은 과연 얼마 남지 않은 수명에 

자신이 진정 원했던 일을 시작할 수 있을까?

그리고 그들이 진정 원했던 것들을 무엇일까?

가끔 이와 비슷한 주제를 말하는 영화나 책들을 보면 자신이 내일 죽게 된다면 하고 싶은 일을 지금 당장하라는 메시지를 던지는 경우가 종종 있다. 

영화 '라스트 홀리데이'도 그와 비슷한 영화이다. 

그러나 실제 삶이 평범한 삶이 아니라면 오늘도 어떻게 될지 모르는 불안한 삶이라면 그들에게 얼마 남지 않은 수명의 의미는 무엇일까?

단순히 자신이 진정 원하는 일을 하라라고 모두에게 똑같이 이야기 할 만큼 사회는 세상은 그렇게 녹록하지 않다. 

그래도 물론 이 영화는 마지막은 기존과 비슷한 결론을 내린다. 

난 아직 잘 모르겠다. 

이 영화가 던지는 메시지의 깊이와 이해를 그리고 나의 생이 얼마 남지 않았다면 진정하고 싶은 것이 무엇인지  

''리뷰'' 카테고리의 다른 글

<여인의 향기>  (0) 2014.11.10
<은하수를 여행하는 히치하이커를 위한 안내서>  (0) 2014.10.26

+ Recent posts