billdwl 发表于 2013-1-29 15:19:31

String Aggregation Techniques

String Aggregation Techniques

On occasion it is necessary to aggregate data from a number of rows into a single row, giving a list of data associated with a specific value. Using the SCOTT.EMP table as an example, we might want to retrieve a list of employees for each department. Below is a list of the base data and the type of output we would like to return from an aggregate query.
Base Data:    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 MILLERDesired Output:    DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 SMITH,FORD,ADAMS,SCOTT,JONES      30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARDThis article is based on a thread from asktom.oracle.com and contains several methods to achieve the desired results.

[*]LISTAGG Analytic Function in 11g Release 2
[*]WM_CONCAT Built-in Function
[*]Specific Function
[*]Generic Function using Ref Cursor
[*]User-Defined Aggregate Function
[*]ROW_NUMBER() and SYS_CONNECT_BY_PATH functions in Oracle 9i
[*]COLLECT function in Oracle 10g

LISTAGG Analystic Function in 11g Release 2

The LISTAGG analytic function was introduced in Oracle 11g Release 2, making it very easy to aggregate strings. The nice thing about this function is it also allows us to order the elements in the concatenated list.
COLUMN employees FORMAT A50SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employeesFROM   empGROUP BY deptno;    DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 ADAMS,FORD,JONES,SCOTT,SMITH      30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD3 rows selected.
WM_CONCAT Built-in Function

If you are not running 11g Release 2, but are running a version of the database where the WM_CONCAT function is present, then it is a zero effort solution as it performs the aggregation for you.
COLUMN employees FORMAT A50SELECT deptno, wm_concat(ename) AS employeesFROM   empGROUP BY deptno;    DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 SMITH,FORD,ADAMS,SCOTT,JONES      30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD3 rows selected.If is actually an example of a user defined aggregate function described below, but Oracle have done all the work for you.

Specific Function

One approach is to write a specific function to solve the problems. The get_employees function listed below returns a list of employees for the specified department.
CREATE OR REPLACE FUNCTION get_employees (p_deptnoinemp.deptno%TYPE)RETURN VARCHAR2ISl_textVARCHAR2(32767) := NULL;BEGINFOR 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 ERRORSThe function can then be incorporated into a query as follows.
COLUMN employees FORMAT A50SELECT deptno,       get_employees(deptno) AS employeesFROM   empGROUP by deptno;    DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 SMITH,JONES,SCOTT,ADAMS,FORD      30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES3 rows selected.To reduce the number of calls to the function, and thereby improve performance, we might want to filter the rows in advance.
COLUMN employees FORMAT A50SELECT e.deptno,       get_employees(e.deptno) AS employeesFROM   (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.
Generic Function using Ref Cursor

An alternative approach is to write a function to concatenate values passed using a ref cursor. This is essentially the same as the previous example, except that the cursor is passed in making it generic, as shown below.
CREATE OR REPLACE FUNCTION concatenate_list (p_cursor INSYS_REFCURSOR)RETURNVARCHAR2ISl_returnVARCHAR2(32767);   l_temp    VARCHAR2(32767);BEGINLOOP    FETCH p_cursor    INTOl_temp;    EXIT WHEN p_cursor%NOTFOUND;    l_return := l_return || ',' || l_temp;END LOOP;RETURN LTRIM(l_return, ',');END;/SHOW ERRORSThe CURSOR function is used to allow a query to be passed to the function as a ref cursor, as shown below.
COLUMN employees FORMAT A50SELECT e1.deptno,       concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employeesFROM   emp e1GROUP BY e1.deptno;    DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 SMITH,JONES,SCOTT,ADAMS,FORD      30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES3 rows selected.Once again, the total number of function calls can be reduced by filtering the distinct values, rather than calling the function for each row.
COLUMN employees FORMAT A50SELECT deptno,       concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employeesFROM   (SELECT DISTINCT deptno      FROM emp) e1;    DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 SMITH,JONES,SCOTT,ADAMS,FORD      30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES3 rows selected.
User-Defined Aggregate Function

The WM_CONCAT function described above is an example of a user-defined aggregate function that Oracle have already created for you. If for some reason you don't want to use the built in WM_CONCAT, you can create your own user-defined aggregate function as described at asktom.oracle.com.
CREATE OR REPLACE TYPE t_string_agg AS OBJECT(g_stringVARCHAR2(32767),STATIC FUNCTION ODCIAggregateInitialize(sctxIN OUTt_string_agg)    RETURN NUMBER,MEMBER FUNCTION ODCIAggregateIterate(self   IN OUTt_string_agg,                                       valueIN      VARCHAR2 )   RETURN NUMBER,MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,                                       returnValueOUTVARCHAR2,                                       flags      IN   NUMBER)    RETURN NUMBER,MEMBER FUNCTION ODCIAggregateMerge(selfIN OUTt_string_agg,                                     ctx2IN      t_string_agg)    RETURN NUMBER);/SHOW ERRORSCREATE OR REPLACE TYPE BODY t_string_agg ISSTATIC FUNCTION ODCIAggregateInitialize(sctxIN OUTt_string_agg)    RETURN NUMBER ISBEGIN    sctx := t_string_agg(NULL);    RETURN ODCIConst.Success;END;MEMBER FUNCTION ODCIAggregateIterate(self   IN OUTt_string_agg,                                       valueIN      VARCHAR2 )    RETURN NUMBER ISBEGIN    SELF.g_string := self.g_string || ',' || value;    RETURN ODCIConst.Success;END;MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,                                       returnValueOUTVARCHAR2,                                       flags      IN   NUMBER)    RETURN NUMBER ISBEGIN    returnValue := RTRIM(LTRIM(SELF.g_string, ','), ',');    RETURN ODCIConst.Success;END;MEMBER FUNCTION ODCIAggregateMerge(selfIN OUTt_string_agg,                                     ctx2IN      t_string_agg)    RETURN NUMBER ISBEGIN    SELF.g_string := SELF.g_string || ',' || ctx2.g_string;    RETURN ODCIConst.Success;END;END;/SHOW ERRORSCREATE OR REPLACE FUNCTION string_agg (p_input VARCHAR2)RETURN VARCHAR2PARALLEL_ENABLE AGGREGATE USING t_string_agg;/SHOW ERRORSThe aggregate function is implemented using a type and type body, and is used within a query.
COLUMN employees FORMAT A50SELECT deptno, string_agg(ename) AS employeesFROM   empGROUP BY deptno;    DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 SMITH,FORD,ADAMS,SCOTT,JONES      30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD3 rows selected.
ROW_NUMBER() and SYS_CONNECT_BY_PATH functions in Oracle 9i

An example on williamrobertson.net uses the ROW_NUMBER() and SYS_CONNECT_BY_PATH functions to achieve the same result without the use of PL/SQL or additional type definitions.
SELECT deptno,       LTRIM(MAX(SYS_CONNECT_BY_PATH(ename,','))       KEEP (DENSE_RANK LAST ORDER BY curr),',') AS employeesFROM   (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 deptnoCONNECT BY prev = PRIOR curr AND deptno = PRIOR deptnoSTART WITH curr = 1;    DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 ADAMS,FORD,JONES,SCOTT,SMITH      30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD3 rows selected.
COLLECT function in Oracle 10g

An example on oracle-developer.net uses the COLLECT function in Oracle 10g to get the same result. This method requires a table type and a function to convert the contents of the table type to a string. I've altered his method slightly to bring it in line with this article.
CREATE OR REPLACE TYPE t_varchar2_tab AS TABLE OF VARCHAR2(4000);/CREATE OR REPLACE FUNCTION tab_to_string (p_varchar2_tabINt_varchar2_tab,                                          p_delimiter   INVARCHAR2 DEFAULT ',') RETURN VARCHAR2 ISl_string   VARCHAR2(32767);BEGINFOR 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;/The query below shows the COLLECT function in action.
COLUMN employees FORMAT A50SELECT deptno,       tab_to_string(CAST(COLLECT(ename) AS t_varchar2_tab)) AS employeesFROM   empGROUP BY deptno;         DEPTNO EMPLOYEES---------- --------------------------------------------------      10 CLARK,KING,MILLER      20 SMITH,JONES,SCOTT,ADAMS,FORD      30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES      3 rows selected.For more information see:

[*]User-Defined Aggregate Functions
[*]asktom.oracle.com
[*]williamrobertson.net
[*]oracle-developer.net - the collect function in 10g
[*]LISTAGG
页: [1]
查看完整版本: String Aggregation Techniques