메뉴 건너뛰기

tnt_db

Oracle 누계를 구하는 또한가지 방법

운영자 2002.09.18 14:21 조회 수 : 2925 추천:15

누계를 구하는 또한가지 방법

조건.
    TABLE A_TB 에 다음과 같은 DATA가 있다.

     A_MON  A_OUT    
     ------ ----------
     199801    1900000
     199802    2000000
     199803    1500000
     199804    1100000
     199805    2100000
     199806    1600000
     199807    1400000
     199808    1700000
     199809    1900000
     199810    1000000
    월별 집계 TABLE이다.
    현재월은 1998년 10월로 10월 현재일까지의 집계금액이 존재하고있다.
    입력 PARAMETER로서 :IN_MON이 있으며 년월을 가지는 6자리 VARCHAR2
    타입이다.

문제.
    현재월이 1998년 10월이므로 1998년 10월까지가 PARAMETER로 들어오면
    해당년도의 1월부터 해당월까지 각월별로 누계값을 보여주고자 한다.
    대신 현재월 이상의 값이 들어오면 잘못된 조건 입력으로 간주하여
    RETURN되는 DATA가 한 건도 없도록 하고싶다.
    참고로 :IN_MON PARAMETER 에 '199810' 이라는 값이 들어오면
    다음과 같은 결과가 나올것이다.
    
     월        실적      
     --------- ----------
     199801       1900000
     199802       3900000
     199803       5400000
     199804       6500000
     199805       8600000
     199806      10200000
     199807      11600000
     199808      13300000
     199809      15200000
     199810      17200000

생각.
    엿보기1.PARAMETER 값에따라 몇건의 DATA를 읽을 것인가를 결정해야한다.
           제일많이 읽어봐야 12개월치 일 것이다.
    엿보기2.입력받은월과 현재월을 비교해서 입력월이 현재월보다 크면
           RETURN되는 결과가 없도록하는 부분도 필요할 것이다.
    엿보기3.먼저 입력년도의 1월부터 입력 해당월까지를 미리 RECORD로
           준비해 놓고 1월DATA 부터 입력월까지의 DATA를 차례로 읽으면서
           준비되어 기다리고 있는 월이 읽혀온 DATA를 포함하는 월보다
           크면 그 값을 준비되어 있는 월에 더해주는 방식을 생각해보자.

해법.
    누계 구하기에서 다루었던 방식과 일맥상통하는 방식이지만 새로운 맛을
    느낄 수 있는 방식이기도 하다.
    편의상 입력월을 '199810' 으로 가정하고 문제를 풀어보자.
    단계1.우선 PARAMETER를 이용하여 해당년도를 떼어내고
          입력된 월만큼의 RECORD의 ROWNUM을 차례로 뒤에 붙여주자
          10개의 RECORD를 만들어주면된다.
          다음과 같이 해보자.
           SELECT SUBSTR(:IN_MON,1,4)||LPAD(TO_CHAR(ROWNUM),2,'0') V_MON
           FROM   A_TB
           WHERE  ROWNUM < 10 + 1
    단계2.해당월이 10월이기 때문에
          ROWNUM < 10 + 1
          이라는 조건이 붙었다.
          10을 입력 PARAMETER에 따라 바뀌도록 만들어보자.
          10 + 1 대신 TO_NUMBER(SUBSTR(:IN_MON,5,2)) + 1
          라고 써주면 된다.
    단계3.만약에 해당월이 현재월보다 크면 ROWNUM < 1과 같이
          되어야 한다.
          IF :IN_MON >= TO_CHAR(SYSDATE,'YYYYMM') THEN
              ROWNUM  < 1
          ELSE
              ROWNUM  < TO_NUMBER(SUBSTR(:IN_MON,5,2)) + 1
          END IF;
          와 같은 형식이다.
          조건절에 그대로 옮겨보면 다음과 같이 된다.

           WHERE ROWNUM
              < DECODE(LEAST(:IN_MON,TO_CHAR(SYSDATE,'YYYYMM')),:IN_MON,
                       TO_NUMBER(SUBSTR(:IN_MON,5,2)) + 1,1)          
    단계4.이제 월별 실적을 읽어 오자.
          다 읽을 필요는 없다.
          해당년도의 1월부터 해당월까지만 필요하다.
          그대로 표현해보자.

           SELECT *
           FROM   A_TB
           WHERE  A_MON  >   SUBSTR(:IN_MON,1,4)
           AND    A_MON  <= :IN_MON
    단계5.위에서 만든 두개의 DATASET으로 CARTESIAN PRODUCT를 만들고
          먼저만든 V_MON을 기준으로 SUM을 하되 V_MON 이 DATA의
          월보다 클경우만 SUM에 포함시켜주자.
          
           SELECT V_MON   월,
                  SUM(DECODE(LEAST(V_MON,A_MON),B.A_MON,
                             A_OUT)
                     )      실적
           FROM   아까만든 DATASET 두개
           WHERE  필요없고
           GROUP BY            
                 V_MON
    단계6.최종 문장은 다음과 같다.
    
          SELECT V_MON   월,
                  SUM(DECODE(LEAST(A.V_MON,B.A_MON),B.A_MON,
                             B.A_OUT)
                     )      실적
           FROM  (SELECT SUBSTR(:IN_MON,1,4)||
                         LPAD(TO_CHAR(ROWNUM),2,'0') V_MON
                   FROM   A_TB
                   WHERE  ROWNUM
                        < DECODE(LEAST(:IN_MON,TO_CHAR(SYSDATE,'YYYYMM')
                                      ),:IN_MON,
                          TO_NUMBER(SUBSTR(:IN_MON,5,2)) + 1,1)
                  ) A,
                 (SELECT *
                  FROM   A_TB
                  WHERE  A_MON  >   SUBSTR(:IN_MON,1,4)
                  AND    A_MON  <= :IN_MON
                 ) B
           GROUP BY
                 A.V_MON  

뒷풀이.
    많은 부분에서 유용하게 사용되는 ROWNUM을 어떻게 CONTROL 하는가에
    대한 좋은 예라고 할 수 있을 것이다.
    열심히 이해하자.
    열심히 안하고 대충 읽어만 봐도 무슨 짓을 하고 있구나 하고 알 수
    있을 것이다.
    이미 초보를 벗어났다는 증거다.
    축하....
    ROW 가 몇개 안되는 경우 이므로 부등호 조인을 이용하지 않고
    사용했지만 부등호 JOIN을 통한 방법이 정석일 것이다.
    그렇게 되면 단계5번에서 SELECT 부분이 바뀌며 WHERE부분이
    추가 될것이다

           SELECT V_MON        월,
                  SUM(A_OUT)   실적          -- 변경부분

           WHERE  B.A_MON <= A.V_MON         -- 추가부분
위로