연산로직 질문드립니다. 1 22 1,117

by 숨바꼭질 [2016.07.21 14:27:04]


MAIN  
LINE NUM Type
10  
20 LINE
30 VALUE
40 VALUE
50 LINE
60 LINE
70 VALUE
80 VALUE
90 LINE
100 VALUE

 

LINE      
LINE NUM operator LINE NUM From LINE NUM To
20 + 30 30
20 - 40 40
50 + 20 20
50 + 90 90
60 + 80 90
60 + 90 90

 

VALUE    
LINE NUM sign Amt
30 + 5
40 + 20
70 + 30
80 + 40
100 - 10

 

Final    
LINE NUM Amt 연산
10 0  
20 -15 LINE 30 - LINE 40
30 5 VALUE 30
40 20 VALUE 40 
50 -25 LINE 20 + LINE 90
60 20 (LINE 80+LINE90) + LINE 90
70 30 VALUE 70
80 40 VALUE 80
90 -10 LINE 100
100 -10 VALUE 100

 

안녕하세요. 가진 지식이 부족해서 질문 남겨봅니다.

 

3개의 테이블 [ MAIN/LINE/VALUE ] 존재합니다.

1. MAIN 테이블은 라인정보와 어떤테이블을 바라봐야 하는지에 대한 유형정보가 있습니다.

2. LINE 테이블은 지정된 라인을 찾아가서 값을 가져와 연산해야되는 데이터가 있습니다.

   (여기서 From To 는 같은 라인이면 한라인만 보여주고, 다른 라인이면 무조건 + 처리가 됩니다.)

3. VALUE 테이브은 각 라인의 값과 그 값에 대한 부호처리정보가 있습니다.

 

마지막 표를 보시면 연산 규칙에 따라 값이 나와야 합니다.

쿼리로는 방법이 생각이 안나서 템프 테이블에 담아서 처리하려고 하니

계산 순서가 뒤죽박죽이다보니 처리하는데 어려움이 있네요....

이전에도 도움 받았는데 염치불구하고 한번더 도움 부탁드립니다!!

by 마농 [2016.07.21 16:37:43]
WITH main AS
(
SELECT 10 line_num, '' type FROM dual
UNION ALL SELECT  20, 'LINE'  FROM dual
UNION ALL SELECT  30, 'VALUE' FROM dual
UNION ALL SELECT  40, 'VALUE' FROM dual
UNION ALL SELECT  50, 'LINE'  FROM dual
UNION ALL SELECT  60, 'LINE'  FROM dual
UNION ALL SELECT  70, 'VALUE' FROM dual
UNION ALL SELECT  80, 'VALUE' FROM dual
UNION ALL SELECT  90, 'LINE'  FROM dual
UNION ALL SELECT 100, 'VALUE' FROM dual
)
, line AS
(
SELECT 20 line_num, '+' operator, 30 line_num_from, 30 line_num_to FROM dual
UNION ALL SELECT 20, '-',  40,  40 FROM dual
UNION ALL SELECT 50, '+',  20,  20 FROM dual
UNION ALL SELECT 50, '+',  90,  90 FROM dual
UNION ALL SELECT 60, '+',  80,  90 FROM dual
UNION ALL SELECT 60, '+',  90,  90 FROM dual
UNION ALL SELECT 90, '+', 100, 100 FROM dual    -- 요 라인이 누락된 듯?
)
, value AS
(
SELECT 30 line_num, '+' sign, 5 amt FROM dual
UNION ALL SELECT  40, '+', 20 FROM dual
UNION ALL SELECT  70, '+', 30 FROM dual
UNION ALL SELECT  80, '+', 40 FROM dual
UNION ALL SELECT 100, '-', 10 FROM dual
)
, tmp(line_num, sign, amt, line_num_from, line_num_to) AS
(
SELECT m.line_num
     , DECODE(l.operator, '-', -1, 1) * DECODE(v.sign, '-', -1, 1) sign
     , NVL(v.amt, 0) amt
     , l.line_num_from
     , l.line_num_to
  FROM main m
  LEFT OUTER JOIN line l
    ON m.type = 'LINE'
   AND m.line_num = l.line_num
  LEFT OUTER JOIN value v
    ON m.type = 'VALUE'
   AND m.line_num = v.line_num
 UNION ALL
SELECT p.line_num
     , p.sign * DECODE(l.operator, '-', -1, 1) * DECODE(v.sign, '-', -1, 1) sign
     , v.amt
     , l.line_num_from
     , l.line_num_to
  FROM tmp p
 INNER JOIN main m
    ON m.line_num BETWEEN p.line_num_from AND p.line_num_to
  LEFT OUTER JOIN line l
    ON m.type = 'LINE'
   AND m.line_num = l.line_num
  LEFT OUTER JOIN value v
    ON m.type = 'VALUE'
   AND m.line_num = v.line_num
)
SELECT line_num
     , SUM(sign * amt) amt
  FROM tmp
 GROUP BY line_num
 ORDER BY line_num
;

 


by 숨바꼭질 [2016.07.21 16:39:02]

감사합니다.

확인해볼게요!


by 숨바꼭질 [2016.07.21 16:57:19]

한가지 질문있습니다.

union all 아래 첫번째 from 의 tmp 에는 값이 있는 상태인가요?


by 마농 [2016.07.21 17:03:03]

재귀 쿼리(Recursive SQL)입니다.
WITH 절에 선언된 tmp 를 WITH 절 안에서 사용하죠.(자기 참조, 재귀 호출)
UNION ALL 의 상단이 루트 레벨이고
UNION ALL 의 하단에서 계층 레벨을 늘려나가는 것입니다.
UNION ALL 의 하단 첫 수행시 tmp 에는 1레벨 정보가 담겨 있겠죠.
UNION ALL 의 하단 두번째 수행시에는 첫 수행 결과인 2레벨 정보가 담겨 있겠네요.
이렇게 차래대로 계층구조를 키워나가는 구문입니다.


by 숨바꼭질 [2016.07.21 17:16:59]

답변 감사합니다.

주신 쿼리가 여기환경에서 수행이 되지 않아 본테이블로 변경해야 될 것 같습니다...


by 마농 [2016.07.21 17:21:38]

재귀 쿼리는 11G R2 부터 가능합니다.


by 마농 [2016.07.21 18:04:54]
-- 10G 용
SELECT line_num
     , SUM(sign * amt) amt
  FROM (SELECT CONNECT_BY_ROOT(m.line_num) line_num
             , DECODE(MOD(REGEXP_COUNT(
               SYS_CONNECT_BY_PATH(l.operator, ',')||v.sign
               , '-'), 2), 1, -1, 1) sign
             , NVL(v.amt, 0) amt
          FROM main m
          LEFT OUTER JOIN line l
            ON m.type = 'LINE'
           AND m.line_num = l.line_num
          LEFT OUTER JOIN value v
            ON m.type = 'VALUE'
           AND m.line_num = v.line_num
         WHERE CONNECT_BY_ISLEAF = 1
         CONNECT BY m.line_num BETWEEN PRIOR l.line_num_from AND PRIOR l.line_num_to
        )
 GROUP BY line_num
 ORDER BY line_num
;

 


by 숨바꼭질 [2016.07.24 18:49:25]

마농님 질문하나 더 여쭤볼게 있습니다..

operator 의 연산식이 +,- 뿐만 아니라 *,/,% 가 있다면 어떤식으로 풀어야 할지 궁금합니다...


by 마농 [2016.07.25 08:35:16]

말로만 설명하지 마시고 예시를 주세요.

기타 다른 연산자는 없나요?

괄호 같은 건 없나요?

% 연산자는 어떤 연산자인가요?


by 숨바꼭질 [2016.07.25 09:41:51]

괄호는 존재하지 않고, 식의 순서를 의미하는 seq 필드가 있습니다.

기타 연산자(괄호는 실제 DB 값)로는 +(+), -(-), ×(*), ÷(/), MOD(%) 5가지가 있습니다.

LINE
LINE NUM seq operator LINE NUM From LINE NUM To
20 10 + 30 30
20 20 - 40 40
50 10 + 20 20
50 20 × 90 90
60 10 + 80 90
60 20 ÷ 70 70

 

Result

   
LINE NUM Amt 연산
10 0  
20 -15 LINE 30 - LINE 40
30 5 VALUE 30
40 20 VALUE 40 
50 150 LINE 20 × LINE 90
60 1 (LINE 80+LINE90) ÷ LINE 70
70 30 VALUE 70
80 40 VALUE 80
90 -10 LINE 100
100 -10 VALUE 100

 


by 숨바꼭질 [2016.08.03 23:12:17]

답변주신 쿼리에서 응용이 가능한지 문의 드립니다...

MASTER    
LINE NUM SIGN_FLAG Type
10 Y  
20 Y Line
30   Value
40   Value
50 Y Line
60 Y Line
70 Y Value
80 Y Value
90 Y Line
100 Y Value

Master 테이블에 SIGN_FLAG 란 필드가 추가되었습니다.

이 필드는 해당 라인을 연산해온 결과 값에 부호를 변경하라는 의미입니다.

위 테이블을 적용하고 계산을 하게 되면, 아래와 같은 결과로 출력되어야 합니다.

Final    
LINE NUM Amt 연산
10 0  
20 15 ( LINE 30 - LINE 40 ) * -1
30 5 VALUE 30
40 20 VALUE 40 
50 -5 (LINE 20 + LINE 90) * -1
60 60 ((LINE 80+LINE90) + LINE 90) * -1
70 -30 VALUE 70 * -1
80 -40 VALUE 80 * -1
90 -10 LINE 100 * -1
100 10 VALUE 100 * -1

 


by jkson [2016.07.22 08:02:12]

우앙 이건 퀴즈로 나와도 재미있었겠네요~


by 마농 [2016.07.22 08:48:07]

by jkson [2016.07.22 11:33:48]

헐~~ 있었군요~~ -0-;


by 마농 [2016.07.25 10:09:45]

seq 는 항상 10, 20 두가지만 존재한다고 봐도 될까요?


by 숨바꼭질 [2016.07.25 10:17:03]

아닙니다. 계속 추가할 수 있습니다.


by 마농 [2016.07.25 11:46:32]
-- 1. 재귀함수 생성 --
CREATE OR REPLACE FUNCTION f_rec_test(v_line_num NUMBER)
RETURN NUMBER
IS
    v_ret1 NUMBER := 0;
    v_ret2 NUMBER := 0;
BEGIN
    BEGIN
        SELECT DECODE(sign, '-', -1, 1) * amt
          INTO v_ret1
          FROM value
         WHERE line_num = v_line_num
        ;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            FOR c1 IN (SELECT *
                         FROM line l
                        WHERE l.line_num = v_line_num
                        ORDER BY seq
                       )
            LOOP
                v_ret2 := 0;
                FOR c2 IN (SELECT *
                             FROM main
                            WHERE line_num BETWEEN c1.line_num_from AND c1.line_num_to
                           )
                LOOP
                    v_ret2 := v_ret2 + f_rec_test(c2.line_num);
                END LOOP;
                IF c1.operator = '+' THEN
                    v_ret1 := v_ret1 + v_ret2;
                ELSIF c1.operator = '-' THEN
                    v_ret1 := v_ret1 - v_ret2;
                ELSIF c1.operator = '×' THEN
                    v_ret1 := v_ret1 * v_ret2;
                ELSIF c1.operator = '÷' THEN
                    v_ret1 := v_ret1 / v_ret2;
                ELSIF c1.operator = '%' THEN
                    v_ret1 := MOD(v_ret1, v_ret2);
                END IF;
            END LOOP;
    END;
    RETURN v_ret1;
END;
/

-- 2. 재귀함수 호출 --
SELECT line_num
     , f_rec_test(line_num) x
  FROM main
;

 


by 숨바꼭질 [2016.08.02 16:36:28]

감사합니다!!


by 마농 [2016.08.04 07:42:40]
-- 1. 재귀함수 생성 --
CREATE OR REPLACE FUNCTION f_rec_test(v_line_num NUMBER, v_sign_flag VARCHAR2)
RETURN NUMBER
IS
    v_ret1 NUMBER := 0;
    v_ret2 NUMBER := 0;
BEGIN
    BEGIN
        SELECT DECODE(sign, '-', -1, 1) * amt
          INTO v_ret1
          FROM value
         WHERE line_num = v_line_num
        ;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            FOR c1 IN (SELECT *
                         FROM line l
                        WHERE l.line_num = v_line_num
                        ORDER BY seq
                       )
            LOOP
                v_ret2 := 0;
                FOR c2 IN (SELECT *
                             FROM main
                            WHERE line_num BETWEEN c1.line_num_from AND c1.line_num_to
                           )
                LOOP
                    v_ret2 := v_ret2 + f_rec_test(c2.line_num);
                END LOOP;
                IF c1.operator = '+' THEN
                    v_ret1 := v_ret1 + v_ret2;
                ELSIF c1.operator = '-' THEN
                    v_ret1 := v_ret1 - v_ret2;
                ELSIF c1.operator = '×' THEN
                    v_ret1 := v_ret1 * v_ret2;
                ELSIF c1.operator = '÷' THEN
                    v_ret1 := v_ret1 / v_ret2;
                ELSIF c1.operator = '%' THEN
                    v_ret1 := MOD(v_ret1, v_ret2);
                END IF;
            END LOOP;
    END;
    IF v_sign_flag = 'Y' THEN
        v_ret1 := v_ret1 * -1;
    END IF;
    RETURN v_ret1;
END;
/

-- 2. 재귀함수 호출 --
SELECT line_num
     , f_rec_test(line_num, sign_flag) x
  FROM main
;

 


by 숨바꼭질 [2016.08.04 09:04:32]

안녕하세요. 마농님.

죄송하지만 위에 10g 용으로 구현하신 쿼리에 추가하는 것은 어려울까요?

추가로 REGEXP_COUNT 함수는 현재 환경에서 사용이 불가능하여 아래로 변경해봤는데

맞는지 확인 가능할까요?

-기존

DECODE(MOD(REGEXP_COUNT(SYS_CONNECT_BY_PATH(l.operator, ',')||v.sign, '-'), 2), 1, -1, 1) sign

-변경

DECODE(MOD(LENGTH(SYS_CONNECT_BY_PATH(l.operator,',')||v.sign) - LENGTH(REPLACE(SYS_CONNECT_BY_PATH(l.operator,',')||v.sign,'-')),2),1,-1,1) as sign


by 마농 [2016.08.04 09:23:13]

쿼리만으로는 여러가지 한계가 있어 함수 생성방법을 알려드린 것입니다.

지금의 상황에서 재귀함수가 딱 제격일 듯 한데요.


by 숨바꼭질 [2016.08.04 12:46:48]

네. 도와주셔서 감사합니다.

재귀함수 처리로 확인해보겠습니다.

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입