How to Use COLLECT with VARCHAR2 Oracle 10g

ESC picture ESC · May 24, 2016 · Viewed 8.6k times · Source

I'm trying to get the COLLECT function to work for me. I'm using 10g and therefore found that LISTAGG and WM_CONCAT will not work (invalid identifier errors). The data I have is for example as follows.

Order  Lot
123    A23088
123    A23089
089    AABBCC
305    120848
305    CCDDYY

What I need returned is as follows

Order   Lot
123     A23088, A23089
089     AABBCC
305     120848, CCDDYY

Using the following, I get the error: TO_STRING is an invalid identifier

TO_STRING ( CAST(COLLECT(DISTINCT LOT) AS varchar2(100)) ) AS LOT

Using the following, I get the error: expected CHAR "inconsistent datatypes: expected %s got %s"

TO_CHAR ( CAST(COLLECT(DISTINCT LOT) AS varchar2(100)) ) AS LOT

Using the following, I get the error: expected NUMBER "inconsistent datatypes: expected %s got %s"

COLLECT(DISTINCT WHSE_LOT)

Is there any way to get this function to work for me?

Answer

Alex Poole picture Alex Poole · May 25, 2016

The collect function creates a nested table, in your case a table of strings, which you would then cast to a specific type - that is, a type defined as a table of varchar2. You can't cast to a single string.

There are some well-known lists of string aggregation techniques, like this one. There is one that uses collect, but you still need the table type and a function to convert the generated table to a delimited string.

Copying that example verbatim:

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;
/

With that type and function you then do:

SELECT tab_to_string(CAST(COLLECT(DISTINCT lot) AS t_varchar2_tab)) AS lot FROM ...

Interestingly, the 10g version of collect doesn't support DISTINCT; it doesn't complain (!?), but leaves duplicates.

You can pass the collection through the set function to remove the duplicates:

SELECT tab_to_string(SET(CAST(COLLECT(DISTINCT lot) AS t_varchar2_tab))) AS lot FROM ...

Quick demo run in 10.2.0.5:

create table table1(order_no number, lot varchar2(10));

insert into table1 values (590288, '2016538');
insert into table1 values (590288, '2016535');
insert into table1 values (590288, '6016535');
insert into table1 values (590288, '2016535');
insert into table1 values (590288, '2016538');

SELECT order_no, tab_to_string(SET(CAST(COLLECT(DISTINCT lot) AS t_varchar2_tab))) AS LOT
FROM table1 WHERE order_no = 590288 GROUP BY order_no;

  ORDER_NO LOT                                              
---------- --------------------------------------------------
    590288 2016538,2016535,6016535