메뉴 건너뛰기

tnt_db

Oracle DATA 병합

운영자 2002.09.18 14:15 조회 수 : 2405 추천:12

DATA 병합

CREATE TABLE  TEST_PART1(
       CDATE     VARCHAR2(8)  NOT NULL,
       CUSTOMER  VARCHAR2(10) NOT NULL,
       SALES     NUMBER,
       CONSTRAINT PART1_PK PRIMARY KRY (CDATE,CUSTOMER)
                        );

INSERT INTO TEST_PART1 VALUES ('20000501','112054',1);                          
INSERT INTO TEST_PART1 VALUES ('20000502','112054',2);                          
INSERT INTO TEST_PART1 VALUES ('20000504','112054',4);                          
INSERT INTO TEST_PART1 VALUES ('20000505','112054',5);                          
INSERT INTO TEST_PART1 VALUES ('20000501','112055',6);                          
INSERT INTO TEST_PART1 VALUES ('20000503','112055',8);                          
INSERT INTO TEST_PART1 VALUES ('20000504','112055',9);                          
INSERT INTO TEST_PART1 VALUES ('20000505','112055',10);                        
INSERT INTO TEST_PART1 VALUES ('20000502','112056',12);                        
INSERT INTO TEST_PART1 VALUES ('20000503','112056',13);                        
INSERT INTO TEST_PART1 VALUES ('20000504','112056',14);                        
INSERT INTO TEST_PART1 VALUES ('20000505','112056',15);

CREATE TABLE  TEST_PART2(
       CDATE     VARCHAR2(8)  NOT NULL,
       CUSTOMER  VARCHAR2(10) NOT NULL,
       SALES     NUMBER,
       CONSTRAINT PART2_PK PRIMARY KRY (CDATE,CUSTOMER)
                        );
INSERT INTO TEST_PART2 VALUES ('20000502','112056',12);                        
INSERT INTO TEST_PART2 VALUES ('20000503','112056',13);                        
INSERT INTO TEST_PART2 VALUES ('20000504','112056',14);                        
INSERT INTO TEST_PART2 VALUES ('20000505','112056',15);                        
INSERT INTO TEST_PART2 VALUES ('20000501','112057',16);                        
INSERT INTO TEST_PART2 VALUES ('20000502','112057',17);                        
INSERT INTO TEST_PART2 VALUES ('20000504','112057',19);                        
INSERT INTO TEST_PART2 VALUES ('20000505','112057',20);                        
INSERT INTO TEST_PART2 VALUES ('20000501','112058',21);                        
INSERT INTO TEST_PART2 VALUES ('20000502','112058',22);                        
INSERT INTO TEST_PART2 VALUES ('20000503','112058',23);                        
INSERT INTO TEST_PART2 VALUES ('20000504','112058',24);

조건.
   1판매장에서
   2000년 5월 1일 부터
   2000년 5월 5일 까지
   일자별/고객별 로 판매수량을 집계한 DATA가
   아래와 같이 존재한다.
   테이블 명칭은 TEST_PART1 이다.

   열 이름                        널?      유형
   ------------------------------ -------- ----
   CDATE                                   VARCHAR2(8)
   CUSTOMER                                VARCHAR2(10)
   SALES                                   NUMBER

   CDATE    CUSTOMER   SALES    
   -------- ---------- ----------
   20000501 112054              1
   20000501 112055              6
   20000502 112054              2
   20000502 112056             12
   20000503 112055              8
   20000503 112056             13
   20000504 112054              4
   20000504 112055              9
   20000504 112056             14
   20000505 112054              5
   20000505 112055             10
   20000505 112056             15

   2판매장에서도 동일한 구조를 가진
   TABLE 과 DATA가 존재한다.
   테이블은 TEST_PART2 이고 DATA 는 아래와 같다.

   CDATE    CUSTOMER   SALES    
   -------- ---------- ----------
   20000501 112057             16
   20000501 112058             21
   20000502 112056             12
   20000502 112058             22
   20000502 112057             17
   20000503 112056             13
   20000503 112058             23
   20000504 112056             14
   20000504 112057             19
   20000504 112058             24
   20000505 112056             15
   20000505 112057             20

   문제를 위해 조건의 예제로 등장하는
   구조와 데이터를 실제 내용보다 단순화 시키고
   변형을 시도했다.
   하지만 문제는 실제 상황에서 빈번히 일어날 수 있는 경우이니
   참고 바란다.

문제.
   각 지점이 동일한 한 고객과 거래를 할 수 있다.
   2000년 05월 01일부터 2000년 05월 05일까지
   각 고객별 매출수량을 1지점과 2지점을 나누어 보여주자.
   최종 결과가 다음과 같이 나오면 된다.

   CUSTOMER   SALES1     SALES2    
   ---------- ---------- ----------
   112054             12          0
   112055             33          0
   112056             54         54
   112057              0         72
   112058              0         90
  
   일반적으로 고객 테이블이 따로 존재하는 것이 정상이지만
   고객 TABLE은 없다고 가정하고 문제를 해결한다.

생각.
   방법은 여러가지가 있을 수 있다.
   그중에 가장 효율적인 방법을 생각해보자.
   여기서는 2가지만 소개한다.
   첫번째 방법은 구조(KEY) 와 DATA를 분리해서 OUTER-JOIN 을 이용하는 것이고
   두번째 방법은 구조를 만들때 DATA도 같이 읽어오는 방법이다.
   엿보기1.첫번째 방법은 양쪽 테이블을 읽어서 해당기간에 존재하는
           모든 고객을 UNIQUE 하게 가져와 구조를 만들어 놓고
           다시 각 테이블의 판매량을 고객별로 SUM 한다.
           그렇게 하면 INLINE VIEW 3개가 만들어진다.
           첫번째는 DATA의 KEY 역할을 하는 고객만을
           두번째는 1판매장의 고객별 판매수량을
           세번째는 2판매장의 고객별 판매수량을 각각 가지고 있게 된다.
           이 각각의 INLINE VIEW를 첫번째 를 기준으로 OUTER-JOIN을 걸면
           문제는 깨끗이 해결된다.
   엿보기2.두번째 방법은 조금 더 간단하다.
           고객(KEY)과 판매량을 읽기위해 한 TABLE을 두번 ACCESS 하지 않고
           한번에 두가지를 같이 읽어오는 방법이다.
           두 테이블을 UNION으로 연결하여 고객별로 SUM을 하되
           단 한가지 주의할 점은 두 테이블에서 읽혀온 서로 다른 판매량이
           차지할 공간을 따로 만들어 두어야 한다는 것이다.

해법.
   두가지 방법을 차례로 SQL로 옮겨보자.
   어려운 문제는 아닌듯 하니 스스로 해결해 보는 것도 괜찮을 듯 하다.
   와중에 더 좋은 방법이 발견된다면 그것이 정답일 것이다.    
   단계1.첫번째 방법부터 시작하자.
         우선 LIST에 나타나야하는 고객을 읽어오자.
            SELECT CUSTOMER
            FROM   TEST_PART1
            WHERE  CDATE BETWEEN '20000501' AND '20000505'
            UNION
            SELECT CUSTOMER
            FROM   TEST_PART2
            WHERE  CDATE BETWEEN '20000501' AND '20000505'
         두개의 TABLE에 존재하는 기간내의 모든 고객이 읽혀와
         UNION 되면서 저절로 UNIQUE하게 SORT가 이루어지게 된다.
         결과는 다음과 같다.

         CUSTOMER  
         ----------
         112054    
         112055    
         112056    
         112057    
         112058    
         5 행이 선택되었습니다
   단계2.1판매장TABLE에서 기간내의 고객별 판매량을 고객별로 SUM 하여
         읽어온다.

            SELECT CUSTOMER,SUM(SALES) SALES1
            FROM   TEST_PART1
            WHERE  CDATE BETWEEN '20000501' AND '20000505'
            GROUP BY CUSTOMER
         결과는 아래와 같다.

         CUSTOMER   SALES1    
         ---------- ----------
         112054             12
         112055             33
         112056             54
         3 행이 선택되었습니다

        2판매장도 마찬가지로 해보면 다음과 같다.

         CUSTOMER   SALES2    
         ---------- ----------
         112056             54
         112057             72
         112058             90
         3 행이 선택되었습니다

        두개의 TABLE을 서로 OUTER-JOIN으로 연결할 수만 있다면
        궂이 고객을 한번 더 읽을 필요가 없다.
        하지만 불행하게도
        WHERE A.CUSTOMER(+) = B.CUSTOMER(+) 와 같은 형식의 OUTER-JOIN은
        불가능하다.
   단계3.따라서 각 판매장을 고객이라는 연결고리를 이용해 연결한다.

         SELECT A.CUSTOMER,
                SALES1,
                SALES2
         FROM (SELECT CUSTOMER
               FROM   TEST_PART1
               WHERE  CDATE BETWEEN '20000501' AND '20000505'
               UNION
               SELECT CUSTOMER
               FROM   TEST_PART2
               WHERE  CDATE BETWEEN '20000501' AND '20000505'
              ) A,
              (SELECT CUSTOMER,SUM(SALES) SALES1
               FROM   TEST_PART1
               WHERE  CDATE BETWEEN '20000501' AND '20000505'
               GROUP BY CUSTOMER
              ) B,
              (SELECT CUSTOMER,SUM(SALES) SALES2
               FROM   TEST_PART2
               WHERE  CDATE BETWEEN '20000501' AND '20000505'
               GROUP BY CUSTOMER
              ) C
         WHERE B.CUSTOMER(+) = A.CUSTOMER
         AND   C.CUSTOMER(+) = A.CUSTOMER

         결과를 보자

         CUSTOMER   SALES1     SALES2    
         ---------- ---------- ----------
         112054             12          
         112055             33          
         112056             54         54
         112057                        72
         112058                        90
         5 행이 선택되었습니다

      고객 DATASET(INLINE-VIEW  A) 을 기준으로 연결이  이루어지는
      B,C DATASET에 해당 고객이 없어도 고객은 가져오라는 뜻으로,
      전형적인 OUTER-JOIN 이다.
      NULL을 영으로 바꿔만 주면 원하는 DATA 이다.
   단계4.두번째 방법을 이용해 보자.
      두번째 방법의 핵심은 고객을 한번 더 읽어야 하는 불편을 줄여
      보자는 것이다.
      결과를 보면 어차피  최종적으로 각 판매장의 판매량을
      SALES1,SALES2 로 분리해서 읽고 있다.
      그렇다면 판매장1 에서는 "고객/판매량1/0"   을 읽어서
               고객별로 SUM하고
               판매장2 에서는 "고객/0/판매량2"   를 읽어서
               고객별로 SUM한후 UNION ALL 해보자.

           SELECT  CUSTOMER,
                   SUM(SALES) SALES1,
                   0          SALES2
           FROM    TEST_PART1
           WHERE   CDATE BETWEEN '20000501' AND '20000505'
           GROUP BY CUSTOMER
           UNION ALL
           SELECT  CUSTOMER,
                   0          SALES1,
                   SUM(SALES) SALES2
           FROM    TEST_PART2
           WHERE   CDATE BETWEEN '20000501' AND '20000505'
           GROUP BY CUSTOMER
      결과는 아래와 같다.

           CUSTOMER   SALES1     SALES2    
           ---------- ---------- ----------
           112054             12          0
           112055             33          0
           112056             54          0
           112056              0         54
           112057              0         72
           112058              0         90
    
   단계5.중복되는 '112056' 고객만이 두개의 ROW가 되어 각 사업장별로
         분리되어 있다.
         위의 결과를 다시 고객별로 SUM해보자.
         IN-LINE VIEW 안에서 한번 밖에서 한번 모두 두번을 똑같은
         COSTOMER로 GROUP BY 하지말고 밖에서 한번만 해보자.

      SELECT CUSTOMER,
             SUM(SALES1) SALES1,
             SUM(SALES2) SALES2
      FROM (
             SELECT  CUSTOMER,
                     SALES      SALES1,
                     0          SALES2
             FROM    TEST_PART1
             WHERE   CDATE BETWEEN '20000501' AND '20000505'
             UNION ALL
             SELECT  CUSTOMER,
                     0          SALES1,
                     SALES      SALES2
             FROM    TEST_PART2
             WHERE   CDATE BETWEEN '20000501' AND '20000505'
           )
      GROUP BY CUSTOMER

      결과는 문제의 답과 같아진다.

      PLAN :
      SELECT STATEMENT Optimizer=CHOOSE
        SORT (GROUP BY)
          VIEW
            UNION-ALL
              TABLE ACCESS (BY INDEX ROWID) OF TEST_PART1
                INDEX (RANGE SCAN) OF PART1_PK (UNIQUE)
              TABLE ACCESS (BY INDEX ROWID) OF TEST_PART2
                INDEX (RANGE SCAN) OF PART2_PK (UNIQUE)

뒷풀이.
    문제의 답은 여러개 있을 수 있다.
    같은 답이라도 사용환경/RESPONSE TIME 등등을 고려해서 가장
    좋은 답을 찾아내는 것이 바로 TUNING 이다.
    하지만 우리가 원하는 결과를 어떻게 이끌어 낼 것인가 하는 것도
    TUNING의 한 가지일 것이다.
    쉽고 간단한 방법이 항상 더 빠른 결과를 만들어 내지는 못하지만,
    같은 결과를 얻기위해 더 적은 노력이 든다면 그또한 그만큼의 가치가
    있는 것이다.
위로