oracle--多行轉爲一行的連接手段

今天遇到將多行轉爲一行的一個操作,多謝oracle開發板的 wildwave 提供了比較通用的解決辦法,同時也將自己搜到的這方面資料整理如下,多是用於連接列值的。
String集聚連接技術

需要將多行轉換爲一行,例子如下:

    基礎數據:
        DEPTNO ENAME
    ---------- ----------
            20 SMITH
            30 ALLEN
            30 WARD
            20 JONES
            30 MARTIN
            30 BLAKE
            10 CLARK
            20 SCOTT
            10 KING
            30 TURNER
            20 ADAMS
            30 JAMES
            20 FORD
            10 MILLER

    預期輸出:

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 SMITH,FORD,ADAMS,SCOTT,JONES
            30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

    * LISTAGG分析函數(11g Release 2)
    * WM_CONCAT內建函數
    * 自定義函數
    * 使用Ref Cursor實現通用函數
    * 用戶自定義聚集函數
    * ROW_NUMBER()和SYS_CONNECT_BY_PATH函數(Oracle 9i)
    * COLLECT函數(Oracle 10g)

LISTAGG分析函數(11g Release 2)

Oracle 11g Release 2介紹了LISTAGG 函數,使得聚集連接字符串變得很容易。並且允許使用我們指定連接串中的字段順序。使用LISTAGG如下:

    COLUMN employees FORMAT A50

    SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
    FROM   emp
    GROUP BY deptno;

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 ADAMS,FORD,JONES,SCOTT,SMITH
            30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

    3 rows selected.

WM_CONCAT內建函數

如果你的Oracle不是11g Release 2,但是支持WM_CONCAT函數,那麼解決上面的問題同樣是小菜一碟,使用WM_CONCAT 函數G如下:

    COLUMN employees FORMAT A50

    SELECT deptno, wm_concat(ename) AS employees
    FROM   emp
    GROUP BY deptno;

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 SMITH,FORD,ADAMS,SCOTT,JONES
            30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

    3 rows selected.

自定義函數

另一個方法是自定義一個函數解決問題。get_employees對於給定部門返回一組員工:

    CREATE OR REPLACE FUNCTION get_employees (p_deptno  in  emp.deptno%TYPE)
      RETURN VARCHAR2
    IS
      l_text  VARCHAR2(32767) := NULL;
    BEGIN
      FOR cur_rec IN (SELECT ename FROM emp WHERE deptno = p_deptno) LOOP
        l_text := l_text || ',' || cur_rec.ename;
      END LOOP;
      RETURN LTRIM(l_text, ',');
    END;
    /
    SHOW ERRORS

    COLUMN employees FORMAT A50

    SELECT deptno,
           get_employees(deptno) AS employees
    FROM   emp
    GROUP by deptno;

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 SMITH,JONES,SCOTT,ADAMS,FORD
            30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

    3 rows selected.

爲了改善性能減少函數調用,我們預先過濾行數。.

    COLUMN employees FORMAT A50

    SELECT e.deptno,
           get_employees(e.deptno) AS employees
    FROM   (SELECT DISTINCT deptno
            FROM   emp) e;

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 SMITH,JONES,SCOTT,ADAMS,FORD
            30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES
           
    3 rows selected.

使用Ref Cursor實現通用函數

另一個可替代的方法是使用遊標變量寫一個函數來連接行值。基本和上面一樣,只是傳入的是一個遊標,所以使得它更通用:.

    CREATE OR REPLACE FUNCTION concatenate_list (p_cursor IN  SYS_REFCURSOR)
      RETURN  VARCHAR2
    IS
      l_return  VARCHAR2(32767);
      l_temp    VARCHAR2(32767);
    BEGIN
      LOOP
        FETCH p_cursor
        INTO  l_temp;
        EXIT WHEN p_cursor%NOTFOUND;
        l_return := l_return || ',' || l_temp;
      END LOOP;
      RETURN LTRIM(l_return, ',');
    END;
    /
    SHOW ERRORS

使用如下:

    COLUMN employees FORMAT A50

    SELECT e1.deptno,
           concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employees
    FROM   emp e1
    GROUP BY e1.deptno;

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 SMITH,JONES,SCOTT,ADAMS,FORD
            30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

    3 rows selected.

同樣的,爲了減少函數調用可以預先顧慮一些行。.

    COLUMN employees FORMAT A50

    SELECT deptno,
           concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employees
    FROM   (SELECT DISTINCT deptno
            FROM emp) e1;

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 SMITH,JONES,SCOTT,ADAMS,FORD
            30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

    3 rows selected.

用戶自定義聚集函數

如果你不想使用內置的函數,你可以自定義聚集函數:

    CREATE OR REPLACE TYPE t_string_agg AS OBJECT
    (
      g_string  VARCHAR2(32767),

      STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
        RETURN NUMBER,

      MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                           value  IN      VARCHAR2 )
         RETURN NUMBER,

      MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                             returnValue  OUT  VARCHAR2,
                                             flags        IN   NUMBER)
        RETURN NUMBER,

      MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                         ctx2  IN      t_string_agg)
        RETURN NUMBER
    );
    /
    SHOW ERRORS


    CREATE OR REPLACE TYPE BODY t_string_agg IS
      STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
        RETURN NUMBER IS
      BEGIN
        sctx := t_string_agg(NULL);
        RETURN ODCIConst.Success;
      END;

      MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                           value  IN      VARCHAR2 )
        RETURN NUMBER IS
      BEGIN
        SELF.g_string := self.g_string || ',' || value;
        RETURN ODCIConst.Success;
      END;

      MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                             returnValue  OUT  VARCHAR2,
                                             flags        IN   NUMBER)
        RETURN NUMBER IS
      BEGIN
        returnValue := RTRIM(LTRIM(SELF.g_string, ','), ',');
        RETURN ODCIConst.Success;
      END;

      MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                         ctx2  IN      t_string_agg)
        RETURN NUMBER IS
      BEGIN
        SELF.g_string := SELF.g_string || ',' || ctx2.g_string;
        RETURN ODCIConst.Success;
      END;
    END;
    /
    SHOW ERRORS


    CREATE OR REPLACE FUNCTION string_agg (p_input VARCHAR2)
    RETURN VARCHAR2
    PARALLEL_ENABLE AGGREGATE USING t_string_agg;
    /
    SHOW ERRORS

使用如下:

    COLUMN employees FORMAT A50

    SELECT deptno, string_agg(ename) AS employees
    FROM   emp
    GROUP BY deptno;

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 SMITH,FORD,ADAMS,SCOTT,JONES
            30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

    3 rows selected.

ROW_NUMBER()和SYS_CONNECT_BY_PATH函數(Oracle 9i)

使用 ROW_NUMBER() 和SYS_CONNECT_BY_PATH 實現:

    SELECT deptno,
           LTRIM(MAX(SYS_CONNECT_BY_PATH(ename,','))
           KEEP (DENSE_RANK LAST ORDER BY curr),',') AS employees
    FROM   (SELECT deptno,
                   ename,
                   ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) AS curr,
                   ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) -1 AS prev
            FROM   emp)
    GROUP BY deptno
    CONNECT BY prev = PRIOR curr AND deptno = PRIOR deptno
    START WITH curr = 1;

        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 ADAMS,FORD,JONES,SCOTT,SMITH
            30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

    3 rows selected.

COLLECT函數(Oracle 10g)

使用COLLECT函數,這個需要一個關聯數組:

    CREATE OR REPLACE TYPE t_varchar2_tab AS TABLE OF VARCHAR2(4000);
    /

    CREATE OR REPLACE FUNCTION tab_to_string (p_varchar2_tab  IN  t_varchar2_tab,
                                              p_delimiter     IN  VARCHAR2 DEFAULT ',') RETURN VARCHAR2 IS
      l_string     VARCHAR2(32767);
    BEGIN
      FOR i IN p_varchar2_tab.FIRST .. p_varchar2_tab.LAST LOOP
        IF i != p_varchar2_tab.FIRST THEN
          l_string := l_string || p_delimiter;
        END IF;
        l_string := l_string || p_varchar2_tab(i);
      END LOOP;
      RETURN l_string;
    END tab_to_string;
    /

使用如下:

    COLUMN employees FORMAT A50

    SELECT deptno,
           tab_to_string(CAST(COLLECT(ename) AS t_varchar2_tab)) AS employees
    FROM   emp
    GROUP BY deptno;
          
        DEPTNO EMPLOYEES
    ---------- --------------------------------------------------
            10 CLARK,KING,MILLER
            20 SMITH,JONES,SCOTT,ADAMS,FORD
            30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES
           
    3 rows selected.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章