Oracle/PL/SQL2014. 4. 23. 09:41

동적으로 순위를 부여하는 쿼리를 작성해보자. 선행 결과에 따라 후행 결과가 변경되기 때문에 쿼리로 작성하기 어려운 요건이다.

 

테스트를 위해 아래와 같이 테이블을 생성하자. 

-- 1
DROP TABLE t1 PURGE;

CREATE TABLE t1 (
    bf VARCHAR2(1) -- 갱신전
  , af VARCHAR2(1) -- 갱신후
  , r1 NUMBER      -- 1순위
  , r2 NUMBER      -- 2순위
  , r3 NUMBER      -- 3순위
);

INSERT INTO t1 VALUES ('A', 'V', 0, 1, 1); -- A에서 V로 갱신된 건은 2순위와 3순위에 해당
INSERT INTO t1 VALUES ('A', 'W', 0, 1, 0);
INSERT INTO t1 VALUES ('A', 'X', 1, 1, 0);
INSERT INTO t1 VALUES ('B', 'W', 1, 1, 0);
INSERT INTO t1 VALUES ('B', 'X', 0, 0, 1);
INSERT INTO t1 VALUES ('C', 'X', 0, 1, 1);
INSERT INTO t1 VALUES ('C', 'Y', 0, 1, 0);
INSERT INTO t1 VALUES ('D', 'Y', 0, 0, 1);
INSERT INTO t1 VALUES ('D', 'Z', 0, 0, 1);
INSERT INTO t1 VALUES ('E', 'Z', 0, 1, 1);
INSERT INTO t1 VALUES ('E', 'V', 0, 1, 0);
COMMIT;

 

아래는 t1 테이블의 r# 열을 행으로 변환하고 rk, bf, af 순으로 정렬한 결과다. 순위를 부여하는 순서는 정렬 순서와 동일하며, 순위가 부여된 bf와 af는 다시 순위가 부여되지 않는다.

-- 2
SELECT b.lv AS rk, a.bf, a.af, ROW_NUMBER () OVER (ORDER BY b.lv, a.bf, a.af) AS rn
  FROM t1 a
     , (SELECT LEVEL AS lv FROM DUAL CONNECT BY LEVEL <= 3) b
 WHERE DECODE (b.lv, 1, a.r1, 2, a.r2, 3, a.r3) = 1;

RK BF AF RN
-- -- -- --
 1 A  X   1 -- Y
 1 B  W   2 -- Y
 2 A  V   3 -- N (RN=1, A)
 2 A  W   4 -- N (RN=1, A)
 2 A  X   5 -- N (RN=1, A)
 2 B  W   6 -- N (RN=2, B)
 2 C  X   7 -- N (RN=1, X)
 2 C  Y   8 -- Y
 2 E  V   9 -- Y
 2 E  Z  10 -- N (RN=9, E)
 3 A  V  11 -- N (RN=1, A)
 3 B  X  12 -- N (RN=1, X)
 3 C  X  13 -- N (RN=1, X)
 3 D  Y  14 -- N (RN=8, Y)
 3 D  Z  15 -- Y
 3 E  Z  16 -- N (RN=9, E)

13 행이 선택되었습니다.

 

아래는 최종 결과다. bf와 af가 중복되지 않고 순차적으로 순위가 부여되어야 한다.

RK BF AF
-- -- --
 1 A  X
 1 B  W
 2 C  Y
 2 E  V
 3 D  Z

 

아래 쿼리는 Recursive Subquery Factoring을 사용했다. 행수가 많을수록 쿼리의 성능이 저하된다.

-- 3
WITH w1 AS (
SELECT b.lv AS rk, a.bf, a.af, ROW_NUMBER () OVER (ORDER BY b.lv, a.bf, a.af) AS rn
  FROM t1 a
     , (SELECT LEVEL AS lv FROM DUAL CONNECT BY LEVEL <= 3) b
 WHERE DECODE (b.lv, 1, a.r1, 2, a.r2, 3, a.r3) = 1)
   , w2 (rk, bf, af, rn, ph) AS (
SELECT a.rk, a.bf, a.af, a.rn, NULL AS ph
  FROM w1 a
 WHERE a.rn = 1
UNION ALL
SELECT b.rk, b.bf, b.af, b.rn
     , CAST (CASE
                 WHEN (    NVL (INSTR (a.ph, a.bf || ','), 0) = 0
                       AND NVL (INSTR (a.ph, a.af || ','), 0) = 0)
                 THEN a.ph || a.bf || ',' || a.af || ','
                 ELSE a.ph
             END AS VARCHAR2 (4000)) AS ph
  FROM w2 a
     , w1 b
 WHERE a.rn = b.rn - 1)
SELECT a.*
  FROM (SELECT a.*
             , CASE
                   WHEN (    NVL (INSTR (a.ph, a.bf || ','), 0) = 0
                         AND NVL (INSTR (a.ph, a.af || ','), 0) = 0)
                   THEN 'Y'
                   ELSE 'N'
               END AS rs
          FROM w2 a) a
 WHERE a.rs = 'Y';

RK BF AF RN PH               RS
-- -- -- -- ---------------- --
 1 A  X   1                  Y
 1 B  W   3 A,X,             Y
 2 C  Y  15 A,X,B,W,         Y
 2 E  V  17 A,X,B,W,C,Y,     Y
 3 D  Z  29 A,X,B,W,C,Y,E,V, Y

5 행이 선택되었습니다.

 

아래는 동일한 논리를 PL/SQL로 작성한 패키지다. 쿼리보다 단순하고 이해하기 쉽다. 행수가 많더라도 성능 저하가 크지 않다.

-- 4
CREATE OR REPLACE PACKAGE pkg1
IS
    TYPE trc_ph IS TABLE OF NUMBER INDEX BY VARCHAR2(1);
    TYPE trc_t1 IS RECORD (rk NUMBER, bf VARCHAR2(1), af VARCHAR2(1));
    TYPE tnt_t1 IS TABLE OF trc_t1;

    FUNCTION fnc1 RETURN tnt_t1 PIPELINED;
END pkg1;
/

CREATE OR REPLACE PACKAGE BODY pkg1
IS
    FUNCTION fnc1
        RETURN tnt_t1 PIPELINED
    IS
        l_t1_rc    trc_t1;
        l_ph_rc    trc_ph;
        l_bf       VARCHAR2(1);
        l_af       VARCHAR2(1);
    BEGIN
        FOR c1 IN (SELECT   b.lv AS rk
                          , a.bf
                          , a.af
                          , ROW_NUMBER () OVER (ORDER BY b.lv, a.bf, a.af) AS rn
                       FROM t1 a
                          , (SELECT LEVEL AS lv FROM DUAL CONNECT BY LEVEL <= 3) b
                      WHERE DECODE (b.lv, 1, a.r1, 2, a.r2, 3, a.r3) = 1
                   ORDER BY 1, 2, 3)
        LOOP
            BEGIN
                l_bf := l_ph_rc (c1.bf);
            EXCEPTION
                WHEN OTHERS THEN l_bf := 0;
            END;

            BEGIN
                l_af := l_ph_rc (c1.af);
            EXCEPTION
                WHEN OTHERS THEN l_af := 0;
            END;

            IF (l_bf = 0 AND l_af = 0)
            THEN
                l_ph_rc (c1.bf) := 1;
                l_ph_rc (c1.af) := 1;
                l_t1_rc.rk := c1.rk;
                l_t1_rc.bf := c1.bf;
                l_t1_rc.af := c1.af;
                PIPE ROW (l_t1_rc);
            END IF;
        END LOOP;
    END fnc1;
END pkg1;
/

 

아래는 패키지를 사용한 결과다.

-- 5
SELECT * FROM TABLE (pkg1.fnc1);

RK BF AF
-- -- --
 1 A  X
 1 B  W
 2 C  Y
 2 E  V
 3 D  Z

5 행이 선택되었습니다.


'Oracle > PL/SQL' 카테고리의 다른 글

가변 IN 조건 값 처리 #1 - PIPELINED 함수  (0) 2018.11.03
중복 할인 함수  (0) 2018.03.04
DBMS_UTILITY.EXPAND_SQL_TEXT 프로시저  (0) 2014.04.19
REFERENCE CURSOR 예제  (0) 2013.09.01
한글 자소 분리  (0) 2012.06.22
Posted by 정희락_