메뉴 건너뛰기

tnt_db

Oracle 한번 읽은 테이블로 백분율 구하기

운영자 2002.09.18 13:45 조회 수 : 4085 추천:15

조건.
    T100 에 다음과 같은 DATA가 들어있다.

    C1     C2        
    ------ ----------
    A              10
    B              20
    C              30
    D              40
    E              50

문제.
    T100 TABLE의 ACCESS 가 한번만 허용된 상태라고 할때
    - 실제로 ACCESS가 한번만 허용될리는 없다.
      단지 조건이 복잡하다거나
      INDEX가 존재하지 않는다거나 하는 여러가지 이유로 두번 읽는것이
      부담스러울 경우가 있다. -
    MAIN TABLE인 T100 을 한번만 읽은 후 가공을 통하여 백분율을
    구해내는 문제다.

    결과 가 다음처럼 나오면 된다.

    C1     C2         C2_RT    
    ------ ---------- ----------
    A              10       6.67
    B              20      13.33
    C              30         20
    D              40      26.67
    E              50      33.33

    대부분의 경우는 오히려 이문제에서 요구하는 답보다
    차라리 TABLE을 한번 더 ACCESS 하여 미리 합을 구해놓고 그 값을
    이용해 백분율을 구하는 방법이 쓰기 간편하고 효과도 뛰어나다.
  
생각.
    이 문제를 주어진 환경 안에서 해결하기 위해 가장먼저 고려 되어야
    할 것은 이제까지 우리가 이용했던 복제이다.
    복제를 하기위해 여태까지 우리는 복제용 테이블을 이용하지 않았다.
    그 이유는 여태까지 우리가 다루었던 주제에서는  주어진 DATA의
    샘플이 그 원리 만을 설명하기에 충분할 만큼만 주어졌기 때문이다.
    하지만 DATA가 매우 많아져서 많은 양의 복제가 일어나야 한다면
    - 그런 경우도 그리 흔하지 않지만
      이번 예제가 그런 경우가 될 수 있다 -
    복제를 하기위해 많은 ROW수를 가진 TABLE에서 ROWNUM을 읽어오는
    방식 보다는 INDEX가 생성되어 있는 복제전용  테이블을 이용하는
    것이 효율적일 수 있다.
    다시 문제를 해결하기 위한 방법으로 돌아와서 생각하자.
    복제가 어떻게 이용될 것인가는 차차 얘기하고 가장 급선무는 역시
    읽어 온 DATA의 전체 합계를 알아야 백분율을 구할 수 있다는 얘기다.
    전체합계 역시 복제를 이용해 구한다.
    전체 합계를 구하는 문제는 여러번 접해 봤기에 더 이상
    설명하지 않겠다.
    전체합계를 구한 후 그 값을 전체 ROW 수 만큼 복제하여 각
    ROW 마다 하나씩 MATCH 시켜주는 단계를 밟는다.
    이때 백율율 계산 대상이 되는 전체 ROW수도 필요하다는 것을
    알 수 있다.
    그래야지만 그 수만큼 복제를 할 수 있기 때문이다.
    그리고 각 ROW와 그 ROW에 연결될 합계 값을 MATCH 시켜줄 GROUPING
    조건을 찾는 것이 문제를 해결하는 가장 중요한 열쇠다.
    그렇게 하고 나면 애초 DATA의 값과 합계값을 이용해 백분율을 구하는
    것은 산수다.
    
해법.
    항상 말로는 설명이 어렵다.
    직접 예제를 통해 알아보자.
    단계1.위에서 언급 되었듯이 첫 단계에서 필요한 정보요소를
    나열하면
          다음과  같다.
          C1    : KEY 역할을 하므로 언제나 필요한 정보다.
          C2    : 백분율을 구할때 분자가 된다
          합계  : 백분율을 구할때 분모가 된다
          ROW수 : 합계를 ROW수 만큼 복제하는 단계에서 유용하게
                  쓰일 정보다.
          한가지 염두에 둘 것은 이번 단계에서 합계가 나오긴 하지만
          그 합계가 하나의 ROW값으로 나온다는 것이다.
          뒤에 그 하나의 ROW를 복제하여 각각의 ROW와 연결 지어 줄
          것이다.  

      SELECT COUNT(B.NO) CNT,               <- ROW수
             MIN(DECODE(B.NO, 1, A.C1)) C1, <- C1(합계는 C1에 NULL)
             SUM(A.C2) C2                   <- C2(합계)
      FROM   T100 A,
            (SELECT ROWNUM NO FROM USER_TABLES WHERE ROWNUM < 3) B
             WHERE  B.NO <= 2
             GROUP BY
                    DECODE(B.NO, 1, A.C1)

          CNT        C1     C2        
          ---------- ------ ----------
                   1 A              10
                   1 B              20
                   1 C              30
                   1 D              40
                   1 E              50
                   5               150

          위의 문장은 바로위와 같은 결과를 RETURN 한다.
          여기서 마지막 ROW가 합계를 가지고 있는 ROW이며,
          원래 DATA의 ROW수에 대한 정보도 가지고 있는 RECORD 이다.
    단계2.위의 상태에서 CNT에 나타난 숫자 만큼만 각 ROW를 복제한다.
          합계정보를 가지고 있는 마지막 ROW만 5번 복제가 되어 나오며
          나머지는 한번의 복제가 일어나므로 결국 자신의 값을 그대로
          가지게 된다.
          이단계가 바로 다량의 복제를 일으킬 수 있는 부분이다.
          현재는 ROW수가 5개밖에 없어서 5번의 복제가 일어나지만,
          건수가 많아진다면,INDEX가 걸린 복제전용 TABLE을 쓰느냐
          그렇지않고 ROW가 그보다 많은 TABLE에서 ROWNUM을
          채취하여 쓰느냐 에 따라 엄청난 속도의 차이를 가져온다.
          현재의 문제에서는 DATA 가 몇건 안되므로 ROWNUM을 채취하여
          사용 하겠다.
          복제를 한 후 다음과 같은 정보를 보자.

          NO     :   복제번호
          ROWNUM :   복제후의 ROWNUM
          CNT    :   합계를 구할때의 CNT
          C1,C2

          SELECT NO,ROWNUM,CNT,C1,C2
          FROM  (SELECT COUNT(B.NO) CNT,
                        MIN(DECODE(B.NO, 1, A.C1)) C1,
                        SUM(A.C2) C2
                 FROM   T100 A,
                       (SELECT ROWNUM NO
                        FROM   USER_TABLES WHERE ROWNUM < 3) B
                 WHERE  B.NO <= 2
                 GROUP BY
                        DECODE(B.NO, 1, A.C1)) X,
                (SELECT ROWNUM NO FROM USER_TABLES) Y
          WHERE  Y.NO <= X.CNT

          NO         ROWNUM     CNT        C1     C2        
          ---------- ---------- ---------- ------ ----------
                   1          1          1 A              10
                   1          2          1 B              20
                   1          3          1 C              30
                   1          4          1 D              40
                   1          5          1 E              50
                   1          6          5               150
                   2          7          5               150
                   3          8          5               150
                   4          9          5               150
                   5         10          5               150

          위의 결과를 살펴보면 원 DATA의 ROW수 와 합계값을 가진
          ROW수가 서로 같다는 것을 알 수 있다.
          그렇다면 DATA 하나에 합계를 하나씩 연결해주는 작업을 해보자.
          다음 단계는 두개(각 DATA와 합계)에 같은 GROUP 단위를
          주어주고 GROUP BY 하는 것이다.
          먼저 연결을 위한 동일 GROUP 단위 부여를 위해 위의 결과를
          자세히 살펴보자.
          모든 RECORD는 ROWNUM이 1부터 차례로 주어져 있다.
          그리고 합계는 NO가 1부터 다시 주어져 있다.
          한가지 문제가 된다면 합계값을 가지는 ROW들이 1부터 다시
          시작되지만,
          합계가 아닌 원 DATA의 NO 값은 모두 1이라는 것이다.
          즉,첫번째 합계를 나타내는 NO 1 이 원DATA의 NO 1과 구분이
          되어져야  한다는 것이다.
          하지만 그것도 그리 큰 문제점은 아니다.
          CNT 가 가진 정보를 보면 그 ROW가 원래 DATA인지 아니면
          합계용 DATA 인지를 쉽계 구분할 수 있기 때문이다.
    단계3.최종적으로 보여야 될 정보는 C1,C2,그리고
          DATA/(DATA의 ROWNUM과 같은 NO를 가진 합계)*100 이다.
          DATA 와 (DATA의 ROWNUM과 같은 NO를 가진 합계) 를
          하나의 ROW로 묶기위해 GROUP BY할 단위는 이미 정해졌다.
          CNT 가 1이면 ROWNUM을 아니면 NO 를 GROUPING 단위로
          주어주면  같은 GROUPING 단위를 갖는 ROW가 DATA 1개 ,
          합계 1개 씩
          총 두개씩이 생긴다.
          QUERY를 다시 다음과 같이 바꿔보자.

          SELECT DECODE(X.CNT, 1, ROWNUM,Y.NO) GRP_UNIT,
                 C1,
                 C2
          FROM  (SELECT COUNT(B.NO) CNT,
                        MIN(DECODE(B.NO, 1, A.C1)) C1,
                        SUM(A.C2) C2
                 FROM   T100 A,
                       (SELECT ROWNUM NO
                        FROM   USER_TABLES WHERE ROWNUM < 3) B
                 WHERE  B.NO <= 2
                 GROUP BY
                        DECODE(B.NO, 1, A.C1)
                ) X,
                (SELECT ROWNUM NO FROM USER_TABLES) Y
          WHERE Y.NO <= X.CNT

          결과가 아래와 같이 나온다.
          GRP_UNIT   C1     C2        
          ---------- ------ ----------
                   1 A              10
                   2 B              20
                   3 C              30
                   4 D              40
                   5 E              50
                   1               150
                   2               150
                   3               150
                   4               150
                   5               150

          이제 GRP_UNIT을 GROUPING 단위로 해서 그룹만 지어지면
          원하는 결과를  얻을 수 있다.
          이때 C1,C2 에는 MIN 또는 MAX 함수를 적용하며 백분율은
          그 두 값을 이용해 계산하고 ROUND 처리를 해준다.
          최종문장은 아래와 같이 된다.

          SELECT MIN(X.C1) C1,
                 MIN(X.C2) C2,
                 ROUND(MIN(C2) / MAX(C2) * 100, 2) C2_RT
          FROM  (SELECT COUNT(B.NO) CNT,
                        MIN(DECODE(B.NO, 1, A.C1)) C1,
                        SUM(A.C2) C2
                 FROM   T100 A,
                       (SELECT ROWNUM NO
                        FROM   USER_TABLES WHERE ROWNUM < 3) B
                 WHERE  B.NO <= 2
                 GROUP BY
                        DECODE(B.NO, 1, A.C1)
                ) X,
                (SELECT ROWNUM NO FROM USER_TABLES) Y
           WHERE Y.NO <= X.CNT
           GROUP BY
                  DECODE(X.CNT, 1, ROWNUM,Y.NO)

뒷풀이.
    한번 더 강조하지만 백분율을 구해야할 대상 DATA 가 많아지면
    INDEX가 있는 복제 전용 TABLE을 사용하는 것이 필수적이다.
    약 만건만 되어도 INDEX 없는 복제는 10분 단위를 넘어서야
    결과가 나온다.
위로