메뉴 건너뛰기

tnt_db

Oracle 수식이용

운영자 2002.09.18 14:12 조회 수 : 2738 추천:20

CREATE TABLE TEST22
      (SER NUMBER NOT NULL,
       A1  NUMBER NOT NULL,
       B1  NUMBER NOT NULL,
       C1  NUMBER NOT NULL,
       D1  NUMBER NOT NULL,
       E1  NUMBER NOT NULL,
       FUN VARCHAR2(100) NOT NULL,
       SOLVE NUMBER,
       CONSTRAINT TEST22_PK PRIMARY KEY (SER)
      );
INSERT INTO TEST22 VALUES (1,10,230,32,430,40,'(A1+B1)/C1-(E1+D1)/10',NULL);
INSERT INTO TEST22 VALUES (2,11,21,40,460,5,'A1+(B1*C1)-E1/10+D1',NULL);
INSERT INTO TEST22 VALUES (3,10,22,540,760,54,'(C1+E1)*(C1+E1)-A1*(D1+B1)',NULL);
INSERT INTO TEST22 VALUES (4,22,430,450,760,52,'B1-C1+A1-(E1*D1)/10',NULL);
INSERT INTO TEST22 VALUES (5,23,50,230,670,5,'A1*B1*C1*D1*E1',NULL);
INSERT INTO TEST22 VALUES (6,45,650,670,780,120,'(A1-B1)-C1+D1/10+E1',NULL);
INSERT INTO TEST22 VALUES (7,100,120,60,780,5,'GREATEST(A1,B1,C1,D1,E1)',NULL);

CREATE TABLE TEST24 (ITEM VARCHAR2(20) NOT NULL,
                     BUDGET VARCHAR2(20) NOT NULL,
                     AMT_AM NUMBER,
                     CONSTRAINT TEST24_PK PRIMARY KEY (ITEM,BUDGET)
                    );
CREATE TABLE TEST25 (BUDGET VARCHAR2(08),
                     SER NUMBER,
                     SEQ NUMBER,
                     SIGNAL VARCHAR2(10),
                     FBUDGET VARCHAR2(08),
                     TBUDGET VARCHAR2(08),
                     CONSTRAINT TEST25_PK PRIMARY KEY (BUDGET,SER,SEQ,SIGNAL)
                    );
INSERT INTO TEST24 VALUES ('제품1','1', 1200);
INSERT INTO TEST24 VALUES ('제품2','1', 11000);
INSERT INTO TEST24 VALUES ('제품1','2', 2100);
INSERT INTO TEST24 VALUES ('제품2','2', 9000);
INSERT INTO TEST24 VALUES ('제품1','3', 4200);
INSERT INTO TEST24 VALUES ('제품2','3', 9300);
INSERT INTO TEST24 VALUES ('제품1','4', 2200);
INSERT INTO TEST24 VALUES ('제품2','4', 6300);
--
INSERT INTO TEST25 VALUES ('4',1,1,'+','1','3');
INSERT INTO TEST25 VALUES ('5',2,1,'+','4','4');
INSERT INTO TEST25 VALUES ('5',2,2,'-','1','1');


수식이용

조건.
    TEST22

    열 이름      유형
    ------------ -------------
     SER            NUMBER
     A1             NUMBER
     B1             NUMBER
     C1             NUMBER
     D1             NUMBER
     E1             NUMBER
     FUN            VARCHAR2(100)
     SOLVE          NUMBER


    SER   A     B     C     D     E     FUN  
    ----- ----- ----- ----- ----- ----- -----------------------------------
         1     10    230     32    430     40 (A1+B1)/C1-(E1+D1)/10
         2     11     21     40    460      5 A1+(B1*C1)-E1/10+D1
         3     10     22    540    760     54 (C1+E1)*(C1+E1)-A1*D1+B1)
         4     22    430    450    760     52 B1-C1+A1-(E1*D1)/10
         5     23     50    230    670      5 A1*B1*C1*D1*E1
         6     45    650    670    780    120 (A1-B1)-C1+D1/10+E1
         7    100    120     60    780      5 GREATEST(A1,B1,C1,D1,E1)

   위와같이 TABLE에 DATA가 있다.
   순서대로 값이있고 적용식이 FUN 이라는 COLUMN에 들어있다.

문제.
    연산식을 이용하여 각 순서별로 결과값을 도출하고 이것을 다시
    SOLVE COLUMN에 UPDATE 하는 것이다.  
    SOLVE COLUMN 이 다음과 같은 결과값을 가지면 된다.

    SER        SOLVE    
    ---------- ----------
             1      -39.5
             2     1310.5
             3     345016
             4      -3950
             5  886075000
             6      -1077
             7        780

생각.
    해당값을 수식에 적용시키고 그 적용식을 이용해 계산값을 뽑아내는
    과정에서 PRODCEDURE와 FUNCTION을 이용한다.
    수식을 만들어 바로 결과를 도출할 수 있다면 좋겠지만 변수안에
    들어있는 수식을 SQL문장안에서 바로 연산식으로 인식시킬 수 있는
    방법을 못 찾았다.
    그러한 방법이 있다면 연산식을 CURSOR를 이용해 SQL로 DBMS에서
    넘겨서 PARSING 시키고 EXECUTE 시키는 FUNCTION도 필요가 없고
    그것을 수행하기 위한 PROCEDURE 도 필요가 없으리라.
    FUNCTION과 PROCEDURE 를 이용했지만 그러한 과정을 거치더라도
    실무에서 충분한 효용성이 있기에 그 예제를 소개하고자 하는
    의도에서 만들어진 문제이다.
    기본적인 사상은 간단하다.
    수식에 들어있는 COLUMN 명칭을 그 COLUMN으로 대체시키고 대체시킨
    연산식이 들어있는 변수를 FUNCTION을 이용해 처리하는 것이다.

해법.
    다음과 같은 FUNCTION을 하나 만들어서 CREATE 한다.

    CREATE OR REPLACE FUNCTION FUNC_VAL(FUNC VARCHAR2) RETURN NUMBER IS
      PL_FUNC   VARCHAR2(100);
      PL_RES    NUMBER;
      PL_SELECT VARCHAR2(100);
      PL_CURSOR INTEGER;
      PL_NUMBER INTEGER;
    BEGIN
      PL_FUNC   := FUNC;
      PL_SELECT := 'SELECT '||PL_FUNC||' AS PL_RES FROM DUAL';
      PL_CURSOR := DBMS_SQL.OPEN_CURSOR;
      DBMS_SQL.PARSE(PL_CURSOR,PL_SELECT,DBMS_SQL.NATIVE);
      DBMS_SQL.DEFINE_COLUMN(PL_CURSOR,1,PL_RES);
      PL_NUMBER := DBMS_SQL.EXECUTE(PL_CURSOR);
      PL_NUMBER := DBMS_SQL.FETCH_ROWS(PL_CURSOR);
      DBMS_SQL.COLUMN_VALUE(PL_CURSOR,1,PL_RES);
      RETURN PL_RES;
    END;

    FUNCTION이 하는 일은 COLUMN값으로 대체된 수식을 받아서
    결과 값을 도출하고 그 결과값을 RETURN 한는 것이다.

    FUNCTION 이 성공적으로 CREATE 되었으면 다음 PROCEDURE를 이용해
    한 RECORD씩 FUNCTION을 이용해 결과값을 구하는 일이다.
    PROCEDURE에서 하는 일을 전체적으로 살펴보면
    첫번째 수식상의 COLUMN명을 실제 COLUMN값으로대체 시키는 과정이
    필요하고,
    두번째 실제값으로 대체된 수식을 함수로 넘겨 결과를 받는 과정이
    필요하며,
    마지막으로 그 결과값을,수식을 가지고 있는 해당 RECORD에 UPDATE
    하는 과정이 필요하다.
    PROCEDURE 는 다음과 같다.

    CREATE OR REPLACE PROCEDURE SOLVE_TEST22 IS
       PL_VAL NUMBER;
    BEGIN
     FOR I IN(SELECT T1.SER,
                     REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(T1.FUN
                      ,'A1',A1),'B1',B1),'C1',C1),'D1',D1),'E1',E1)   FUN
              FROM TEST22 T1)
     LOOP
       PL_VAL := FUNC_VAL(I.FUN);
       UPDATE TEST22
       SET    SOLVE =  PL_VAL
       WHERE  SER   =  I.SER;
     END LOOP;
    END;

뒷풀이.
    이용하기에 따라서는 편리성과 유용성과 유연성을 극대화 시킬 수 있는
    방법이기도 하다.
    기억해 놓았다가 실무에서 유용하게 이용하기를 바란다.
    참고로 기존에 많이 사용하던 방식을 소개하면 유연성면에서 떨어지지만,
    다음과 같은 방법이 있다.

    TABLE : TEST24      

    ITEM                 BUDGET               AMT_AM    
    -------------------- -------------------- ----------
    제품1                1                          1200
    제품2                1                         11000
    제품1                2                          2100
    제품2                2                          9000
    제품1                3                          4200
    제품2                3                          9300
    제품1                4                          2200
    제품2                4                          6300

    TABLE : TEST25

    BUDGET   SER        SEQ        SIGNAL     FBUDGET  TBUDGET
    -------- ---------- ---------- ---------- -------- --------
    4                 1          1 +          1        3      
    5                 2          1 +          4        4      
    5                 2          2 -          1        1      

    TEST24 TABLE에 제품별로 항목 1,2,3,4 가 존재하고 항목별 금액이
    존재한다.
    TEST25는 TEST24에 새로운 항목을 계산해 INSERT또는 UPDATE 한다.
    - BUDGET은 TEST24에 INSERT또는 UPDATE 될 항목이며,
    - SER은 계산이 일어나는 순서이다.
      즉 SER 이 2인 DATA는 SER이 1인DATA의 계산이 먼저 이루어져서
      TEST24에 INSERT또는 UPDATE 된 후에 그값을 이용해서
      해당 항목의 값을 만들어내야한다.
    - SEQ는 연산순서이다.
      연산 부호가 무엇이든간에 SEQ순서대로 연산이 이루어지는 것이다.
    - SIGNAL은 연산부호이다.
    - FBUDGET과 TBUDGET은 연산을 기위한 FROM과 TO 항목의 값이다.
      FROM 과 TO 사이에 있는 모든항목을 SUM한 후 연산 부호에따라
      해당 RECORD의 값과 연산을 수행한다.
    - 따라서 TEST25의 DATA가 요구하고 있는것은
      TEST24의 항목 1에서 3까지를 더해서 항목4를 만들고
      다시 항목4를 이용해서 항목5를 만든후에 항목5에서 항목1만큼을
      빼겠다는 것이다.
    - TEST25를 반영한 결과가 다음과 같이 나오면된다.

      ITEM                 BUDGET               AMT_AM    
      -------------------- -------------------- ----------
      제품1                1                          1200
      제품2                1                         11000
      제품1                2                          2100
      제품2                2                          9000
      제품1                3                          4200
      제품2                3                          9300
      제품1                4                          7500
      제품2                4                         29300
      제품1                5                          6300
      제품2                5                         18300
      
    다음과 같은 PROCEDURE를 만들어서 실행시켜보자.
    원하는 결과가 나올것이다.

     CREATE OR REPLACE PROCEDURE BUDGET_CALC AS
        V_AMT  NUMBER       := 0;
     BEGIN
        DELETE FROM  TEST24
        WHERE  BUDGET IN (SELECT DISTINCT BUDGET
                          FROM   TEST25 );
--            
        FOR I IN(SELECT SER,
                        BUDGET,
                        SEQ,
                        SIGNAL,
                        FBUDGET,
                        TBUDGET
                 FROM   TEST25
                 ORDER BY
                        SER,
                        BUDGET,
                        SEQ     )
        LOOP
        /*DATA중 FROM항목과 TO항목 사이의 값을 부문/제품별로 SUM한다. */
        FOR J IN(SELECT ITEM,SUM(AMT_AM) AMT_AM
                 FROM   TEST24
                 WHERE  BUDGET BETWEEN I.FBUDGET AND I.TBUDGET
                 GROUP BY
                        ITEM)
        LOOP
           /* 계산식을 적용하고자하는 항목을
              읽어온 제품별로 값을 먼저 가져다 놓는다*/
           DECLARE
           BEGIN
             SELECT  AMT_AM
             INTO    V_AMT
             FROM    TEST24
             WHERE   ITEM   = J.ITEM
             AND     BUDGET = I.BUDGET;
           EXCEPTION
                WHEN NO_DATA_FOUND THEN
                /* 해당항목의 제품에 값이 없으면 계산식의 연산자를  기준으로
                   연산식의 앞에오는 값은  영이다 */
                V_AMT := 0;
           END;
           /* 계산식의 부호에 따라 연산을 수행한다 */
           IF    I.SIGNAL = '+'  THEN
                 V_AMT := V_AMT+J.AMT_AM;
           ELSIF I.SIGNAL = '-'  THEN
                 V_AMT := V_AMT-J.AMT_AM;
           ELSIF I.SIGNAL = '*'  THEN
                 V_AMT := V_AMT*J.AMT_AM;
           ELSIF I.SIGNAL = '/'  THEN
                 V_AMT := V_AMT/J.AMT_AM;
           END IF;
           /* 수행한 연산값을 해당 KEY를 기준으로 UPDATE 하고
              해당값이 없으면 INSERT 한다 */
           UPDATE TEST24
           SET    AMT_AM  = V_AMT
           WHERE  ITEM    = J.ITEM
           AND    BUDGET  = I.BUDGET;
           IF SQL%NOTFOUND  THEN
              INSERT INTO TEST24(ITEM,BUDGET,AMT_AM)
              VALUES (J.ITEM,I.BUDGET,V_AMT);
           END IF; /*END IF SQL%NOTFOUND THEN */
        END LOOP;  /*END LOOP FOR J IN()*/
      END LOOP;        /*END LOOP FOR I IN()*/
    END;        
    
    실무에서 도움이 되길 바란다
위로