Skip to Main Content
  • Questions
  • DBMS_UTILITY.COMMA_TO_TABLE limitations ?

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Neelabh.

Asked: February 08, 2001 - 3:02 pm UTC

Last updated: July 06, 2009 - 6:35 pm UTC

Version: 8.1.6

Viewed 10K+ times! This question is

You Asked

Hi Tom,

when I use the following code :
------------------------------------------------
CREATE OR REPLACE procedure pr1
as
mytab dbms_utility.uncl_array;
mystring varchar2(200);
len binary_integer;
begin
mystring := 'yes,no,this,that,is';
dbms_utility.COMMA_TO_TABLE(mystring,len,mytab);

for i in 1..len
loop
dbms_output.put_line('The '||i||' name is '||substr(mytab(i),1,25));
end loop;
end;
/
-----------------------------------------------
It gives me the following error
SQL> exec pr1
BEGIN pr1; END;

*
ERROR at line 1:
ORA-00931: missing identifier
ORA-06512: at "SYS.DBMS_UTILITY", line 79
ORA-06512: at "SYS.DBMS_UTILITY", line 108
ORA-06512: at "NEELABH.PR1", line 18
ORA-06512: at line 1


while if I replace line 7 which is
mystring := 'yes,no,this,that,is';
BY
mystring := 'yes,no,this,that,are';
it works to give me the following

SQL> exec pr1
The 1 name is yes
The 2 name is no
The 3 name is this
The 4 name is that
The 5 name is are

PL/SQL procedure successfully completed.

Why is the behaviour like this. Are there some limitations in the string values which one can use ??

Regards
Neelabh

and Tom said...

In a nutshell, these procedures do not do what you think. These were built for replication and parsing of snapshot group tablename strings. They parse IDENTIFIERS. In order to understand COMMA_TO_TABLE, you need to understand NAME_TOKENIZE:

NAME_TOKENIZE

This utility routine simply takes a string that represents some object name and breaks it into its component pieces for you. Objects are referenced via:

[schema].[object_name].[procedure|function]@[database link]

NAME_TOKENIZE simply takes a string in that form and breaks it out into the three leading pieces and the last (database link) piece. Additionally, it tells us what byte it stopped parsing the object name at. Here is a small example showing what you can that you might expect back from various object names you pass to it. Note that you do not have to use REAL object names (these tables and procedures do not have to exist) – but you must use VALID object identifiers. If you do not use a valid object identifier, NAME_TOKENIZE will raise an error. That makes NAME_TOKENIZE suitable as a method to discover whether a given string of characters will be a valid identifier or not:

scott@TKYTE816> declare
2 l_a varchar2(30);
3 l_b varchar2(30);
4 l_c varchar2(30);
5 l_dblink varchar2(30);
6 l_next number;
7
8 type vcArray is table of varchar2(255);
9 l_names vcArray :=
10 vcArray( 'owner.pkg.proc@database_link',
11 'owner.tbl@database_link',
12 'tbl',
13 '"Owner".tbl',
14 'pkg.proc',
15 'owner.pkg.proc',
16 'proc',
17 'owner.pkg.proc@dblink with junk',
18 '123' );
19 begin
20 for i in 1 .. l_names.count
21 loop
22 begin
23 dbms_utility.name_tokenize(name => l_names(i),
24 a => l_a,
25 b => l_b,
26 c => l_c,
27 dblink => l_dblink,
28 nextpos=> l_next );
29
30 dbms_output.put_line( 'name ' || l_names(i) );
31 dbms_output.put_line( 'A ' || l_a );
32 dbms_output.put_line( 'B ' || l_b );
33 dbms_output.put_line( 'C ' || l_c );
34 dbms_output.put_line( 'dblink ' || l_dblink );
35 dbms_output.put_line( 'next ' || l_next || ' ' ||
36 length(l_names(i)));
37 dbms_output.put_line( '-----------------------' );
38 exception
39 when others then
40 dbms_output.put_line( 'name '||l_names(i) );
41 dbms_output.put_line( sqlerrm );
42 end;
43 end loop;
44 end;
45 /
name owner.pkg.proc@database_link
A OWNER
B PKG
C PROC
dblink DATABASE_LINK
next 28 28

As you can see – this breaks out the various bits and pieces of our object name for us. Here the NEXT is set to the length of the string – parsing ended when we hit the end of the string in this case. Since we used every possible piece of the object name, all 4 components are filled in. Now for the remaining examples:

name owner.tbl@database_link
A OWNER
B TBL
C
dblink DATABASE_LINK
next 23 23
-----------------------
name tbl
A TBL
B
C
dblink
next 3 3
-----------------------

Notice here how B and C are left NULL. Even though an object identifier is SCHEMA.OBJECT.PROCEDURE, NAME_TOKENIZE makes no attempt to put the “TBL” into the B out parameter. It simply takes the first part it finds and puts it in A, the next into B and so on. A, B and C do not represent specific pieces of the object name – just the first found, next found and so on.

name "Owner".tbl
A Owner
B TBL
C
dblink
next 11 11
-----------------------

Here is something interesting. In the previous examples, NAME_TOKENIZE uppercased everything. That is because identifiers are in upper case unless you use QUOTED identifiers. Here, we used a quoted identifier and NAME_TOKENIZE will preserve that for us and remove the quotes!

name pkg.proc
A PKG
B PROC
C
dblink
next 8 8
-----------------------
name owner.pkg.proc
A OWNER
B PKG
C PROC
dblink
next 14 14
-----------------------
name proc
A PROC
B
C
dblink
next 4 4
-----------------------
name owner.pkg.proc@dblink with junk
A OWNER
B PKG
C PROC
dblink DBLINK
next 22 31
-----------------------

There is an example where the parsing stopped BEFORE we ran out of string. NAME_TOKENIZE is telling us it stopped parsing at byte 22 out of 31. That is the space right before “with junk”. It simply ignores the remaining pieces of the string for us.

name 123
ORA-00931: missing identifier

PL/SQL procedure successfully completed.

And lastly, this shows if we use an invalid identifier, NAME_TOKENIZE will throw and exception. It checks all tokens for being valid identifiers before returning. That makes it useful as a tool to validate object names if you are building an application that will create objects in the Oracle database. For example, if you are building a data modelling tool and would like to validate that the name the end user wants to use for a table or column name is valid – NAME_TOKENIZE will do the work for you)



COMMA_TO_TABLE, TABLE_TO_COMMA


These two utilities either take a comma delimited string of IDENTIFIERS and parse them into a PLSQL table (COMMA_TO_TABLE) or table a PLSQL table of any type of string and make a comma delimited string of them (TABLE_TO_COMMA). I stress the work IDENTIFIERS above because COMMA_TO_TABLE uses NAME_TOKENIZE to parse the strings – hence as we saw in that section, we need to use valid Oracle identifiers (or quoted identifiers). This still limits us to 30 characters per element in our comma-delimited string however.

This utility is most useful for applications that want to store a list of tablenames in a single string for example and have them easily converted to an array in PLSQL at runtime. Otherwise, it is of limited use.

Here is an example using this routine and demonstrating how it deals with long identifiers and invalid identifiers:

scott@TKYTE816> declare
2 type vcArray is table of varchar2(4000);
3
4 l_names vcArray := vcArray( 'emp,dept,bonus',
5 'a, b, c',
6 '123, 456, 789',
7 '"123", "456", "789"',
8 '"This is a long string, longer then 32 characters","b",c');
9 l_tablen number;
10 l_tab dbms_utility.uncl_array;
11 begin
12 for i in 1 .. l_names.count
13 loop
14 dbms_output.put_line( chr(10) ||
15 '[' || l_names(i) || ']' );
16 begin
17
18 dbms_utility.comma_to_table( l_names(i),
19 l_tablen, l_tab );
20
21 for j in 1..l_tablen
22 loop
23 dbms_output.put_line( '[' || l_tab(j) || ']' );
24 end loop;
25
26 l_names(i) := null;
27 dbms_utility.table_to_comma( l_tab,
28 l_tablen, l_names(i) );
29 dbms_output.put_line( l_names(i) );
30 exception
31 when others then
32 dbms_output.put_line( sqlerrm );
33 end;
34 end loop;
35 end;
36 /

[emp,dept,bonus]
[emp]
[dept]
[bonus]
emp,dept,bonus

So, that shows that it can take the string “emp,dept,bonus” and break it into a table and put it back together again.

[a, b, c]
[a]
[ b]
[ c]
a, b, c

This example shows that if you have white space in the list, it will be preserved. You would have to use the rtrim function to remove leading white space if you do not want any.

[123, 456, 789]
ORA-00931: missing identifier

This shows that to use this procedure on a comma delimited string of numbers, we must go one step further as demonstrated below:

["123", "456", "789"]
["123"]
[ "456"]
[ "789"]
"123", "456", "789"

Here is is able to extract the numbers from the string. Note however how it not only retains the leading whitespace but it also retains the quotes. It would be upto you to remove them if you so desire.

["This is a long string, longer then 32 characters","b",c]
ORA-00972: identifier is too long

PL/SQL procedure successfully completed.

And this last example shows that if the identifier is too long (longer then 30 characters) it will raise an error as well – these routines are only useful for strings of 30 characters or less. While it is true that TABLE_TO_COMMA will take larger strings then 30 characters, COMMA_TO_TABLE will not be able to undo that work.


Rating

  (15 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

a question of reserverd words?

Andrew, August 10, 2001 - 5:15 pm UTC

The original example showed one string which worked and one which didn't. I guess the conclusion is that the example which failed did so because "is" is a reserved word. "are" is not.
Naming rules:
</code> http://www.oradoc.com/ora816/server.816/a76989/ch29.htm#27571
Reserved words:
http://www.oradoc.com/ora816/server.816/a76989/ap_keywd.htm#617080 <code>

why isnt this working

A reader, August 15, 2003 - 11:36 am UTC

SQL> var lv1 varchar2(30);
SQL> var lv2 varchar2(30);
SQL>  var lv3 varchar2(30);
SQL> var lv4 varchar2(30);
SQL> var lv5 number;
SQL> var lv6 number;
SQL> exec dbms_utility.name_resolve('EMP',1,:lv1,:lv2,:lv3,:lv4,:lv5,:1v6) ;
SP2-0552: Bind variable "1" not declared.

Tom , I expec to see the details of 'EMP' table, but it is not happening so.

Can you please explain why? 

Tom Kyte
August 15, 2003 - 12:01 pm UTC

:1v6
^

you gotta one there.

why is my local table 'emp' not being recognized by dbms_utility.name_resolve

A reader, August 15, 2003 - 1:48 pm UTC

SQL> @LOCAL_OBJECTS

OBJECT_NAME                                        OBJECT_TYPE
-------------------------------------------------- -------------------------------------------------
BONUS                                              TABLE
CONTRACTS                                          TABLE
DEPT                                               TABLE
DEPT_PK                                            INDEX
DUMMY                                              TABLE
EMP                                                TABLE
PLAN_TABLE                                         TABLE
SALGRADE                                           TABLE
T                                                  TABLE
TEST                                               TABLE

10 rows selected.

SQL> DESC EMP
 Name                                                  Null?    Type
 ----------------------------------------------------- -------- ------------------------------------
 EMPNO                                                 NOT NULL NUMBER(4)
 ENAME                                                          VARCHAR2(10)
 JOB                                                            VARCHAR2(9)
 MGR                                                            NUMBER(4)
 HIREDATE                                                       DATE
 SAL                                                            NUMBER(7,2)
 COMM                                                           NUMBER(7,2)
 DEPTNO                                                         NUMBER(2)

SQL> exec dbms_utility.name_resolve('EMP',1,:lv1,:lv2,:lv3,:lv4,:lv5,:lv6) ;
BEGIN dbms_utility.name_resolve('EMP',1,:lv1,:lv2,:lv3,:lv4,:lv5,:lv6) ; END;

*
ERROR at line 1:
ORA-06564: object EMP does not exist
ORA-06512: at "SYS.DBMS_UTILITY", line 68
ORA-06512: at line 1
 

Tom Kyte
August 15, 2003 - 3:52 pm UTC

The dbms_utility.name_resolve procedure is supported only for procedures, functions, and packages, not tables. Note the valid return types for
part1_type - 5,6,7 and 9.

is dbms_utility.name_resolve useful for a regular developer

A reader, August 15, 2003 - 7:09 pm UTC

is dbms_utility.name_resolve useful for a regular developer, if so in what way..

full scan takes place

Vipin, September 04, 2003 - 1:54 pm UTC

Hi Tom,

Creating a type of table and accessing the comma delimited parameter in IN clause is the best way of handling these scenarios. But I did the following testcase and realized that the approach always go for a full table scan if the statistics are available and time taken is also pretty high.

Please see the test case below:-

SQL>  Create table t
  2   (id number primary key,
  3   name varchar2(100))
  4  
SQL> /

Table created.

SQL> 
SQL>  
SQL>  INSERT INTO t
  2   SELECT Id, NAME
  3   FROM   big_table
  4  
SQL> /

516430 rows created.

SQL> commit;

Commit complete.

SQL> 
SQL> select count(*)
  2  from   t
  3  
SQL> /

  COUNT(*)
----------
    516430

SQL> 
SQL> set autotrace on
SQL> set timing on
SQL> 
SQL> 
SQL> CREATE OR REPLACE TYPE my_table IS TABLE OF NUMBER;
  2  /

Type created.

Elapsed: 00:00:00.07
SQL> SELECT * FROM t
  2  WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  3  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3


Elapsed: 00:00:00.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     VIEW OF 'VW_NSO_1'
   3    2       SORT (UNIQUE)
   4    3         COLLECTION ITERATOR (CONSTRUCTOR FETCH)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'T'
   6    5       INDEX (UNIQUE SCAN) OF 'SYS_C008705' (UNIQUE)




Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         21  consistent gets
          3  physical reads
         68  redo size
        520  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          2  rows processed



Please observe that the time taken is 0.06 seconds and INDEX UNIQUE SCAN is used.




SQL> analyze table t compute statistics for table for all indexes for all indexed columns;

Table analyzed.

Elapsed: 00:00:13.04
SQL> SELECT * FROM t
  2  WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  3  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3


Elapsed: 00:00:01.09

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5680951 Card=1 Bytes
          =30)

   1    0   NESTED LOOPS (SEMI) (Cost=5680951 Card=1 Bytes=30)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=221 Card=516430 Bytes=1
          5492900)

   3    1     COLLECTION ITERATOR (CONSTRUCTOR FETCH)




Statistics
----------------------------------------------------------
         22  recursive calls
          0  db block gets
       2289  consistent gets
        814  physical reads
          0  redo size
        520  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed



Please observe that the time taken is 1.09 seconds and TABLE FULL SCAN is used.



SQL> 
SQL> 
SQL> 
SQL> analyze table t delete statistics;

Table analyzed.

Elapsed: 00:00:00.00
SQL> 
SQL> 
SQL> SELECT * FROM t
  2  WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  3  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3


Elapsed: 00:00:00.05

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     VIEW OF 'VW_NSO_1'
   3    2       SORT (UNIQUE)
   4    3         COLLECTION ITERATOR (CONSTRUCTOR FETCH)
   5    1     TABLE ACCESS (BY INDEX ROWID) OF 'T'
   6    5       INDEX (UNIQUE SCAN) OF 'SYS_C008705' (UNIQUE)




Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          1  physical reads
          0  redo size
        520  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          2  rows processed

SQL> 


Please observe that the time taken is 0.05 seconds and INDEX UNIQUE SCAN is used.

Could you please explain why table full scan is used when statistics are available and more over doesn't this limit us from using this technique.

 

Tom Kyte
September 05, 2003 - 2:00 pm UTC

try first_rows optimization

Still having problem

Vipin, September 05, 2003 - 6:45 pm UTC

Hi Tom,

Please find the results of trial with the first_rows hint:-

SQL> SELECT /*+first_rows */ T.*
  2  FROM t
  3      WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  4  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3


Elapsed: 00:00:01.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=5680951 Ca
          rd=1 Bytes=30)

   1    0   NESTED LOOPS (SEMI) (Cost=5680951 Card=1 Bytes=30)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=221 Card=516430 Bytes=1
          5492900)

   3    1     COLLECTION ITERATOR (CONSTRUCTOR FETCH)




Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2287  consistent gets
         88  physical reads
          0  redo size
        520  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

SQL> SELECT T.*
  2  FROM t
  3      WHERE id IN (SELECT * FROM table(CAST(my_table(34,78) AS my_table)))
  4  /

        ID
----------
NAME
--------------------------------------------------------------------------------
        34
/11799933_SchemaProtectionDoma

        78
/12f385b1_DefaultCellEditor3


Elapsed: 00:00:01.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5680951 Card=1 Bytes
          =30)

   1    0   NESTED LOOPS (SEMI) (Cost=5680951 Card=1 Bytes=30)
   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=221 Card=516430 Bytes=1
          5492900)

   3    1     COLLECTION ITERATOR (CONSTRUCTOR FETCH)




Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2287  consistent gets
         88  physical reads
          0  redo size
        520  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed


As you see there is no much of a difference. Could you please explain why is the optimizer going for a full scan here? And if this is the case I think this technique is not able to handle the requirement. Please comment. 

Tom Kyte
September 05, 2003 - 7:13 pm UTC

use my buddy rownum:

big_table@ORA920> select /*+ first_rows */ * from big_table
2 where id in ( select * from table(my_table(1,2,3)) where rownum >= 0 )
3 /

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=16427 Card=8168 Bytes=841304)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=3 Card=1 Bytes=90)
2 1 NESTED LOOPS (Cost=16427 Card=8168 Bytes=841304)
3 2 VIEW OF 'VW_NSO_1' (Cost=12 Card=8168 Bytes=106184)
4 3 SORT (UNIQUE)
5 4 COUNT
6 5 FILTER
7 6 COLLECTION ITERATOR (CONSTRUCTOR FETCH)
8 2 INDEX (RANGE SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2 Card=1)


i tend to use it on queries against a collection to avoid calling the plsql stuff over and over...

A reader, September 06, 2003 - 6:04 pm UTC

Hi Tom
could you please explain what is happening in the above case. How optimizer is influenced by the rownum methode and how this leads to do an index scan over the outer query.

Thanks

Tom Kyte
September 06, 2003 - 6:20 pm UTC

rownum is a non-mergable "thing", like an aggregate.

so, the subquery is materialized to temp - totally different processing takes place

so, a temporary table is better here?

sury, November 11, 2003 - 3:00 am UTC

Tom,

clarification for your last follow-up:

"rownum is a non-mergable "thing", like an aggregate.

so, the subquery is materialized to temp - totally different processing takes
place"

so, the values 1,2,3

of the clause
"in ( select * from table(my_table(1,2,3)) "

are put into a temporary table and the the sub-query is executed?

why i asked this question is, i have similar requirement, where in i need to parse the comma delimited string, and put into a nested table and use that in the subquery like above. but is it better to use a temporary table to hold the parsed values, so that temporary table can be used in the subquery?


Tom Kyte
November 11, 2003 - 7:16 am UTC



if you use rownum, it'll just happen automagically for you.

Once more about name_resolve

Oleksandr Alesinskyy, December 15, 2003 - 5:37 pm UTC

First of all it is very strange that context parameter is not really documnted. Doc states that it shall be between 0 and 9, but procedure itself allows only from 1 to 7 (on 0,8 and 9 it raises exception with list of permitted values in message text).

Secondly, meaning of context values is unknown (and undocumented). In one of the replies above was an attempt to use procedure to resolve name of emp table without success with context speciifed as 1. But if repeat this attempt with context equal to 2 ateempt will sucsseed. And so on and so forth.

Is there a way to resolve name save the direct iterationover dicitionary views?

Tom Kyte
December 16, 2003 - 6:48 am UTC

well -- it seems you discovered a way -- for tables (and synonyms that point to tables) and views -- a parameter of "2" does seem to do it.



Again about name resolve

Oleksandr Alesinskyy, December 16, 2003 - 8:25 am UTC

Yes, I have discovered a way, but I have to know object type in advance (p.e. with context = 2 synonyms for packages aren't resolved). I dare to say that name_resolve is a crap (at least with existing "documentation").
.

First_row

Vinnie, December 01, 2004 - 2:31 pm UTC

I have been looking through the examples above having little success.

I have the following:

SELECT /*+ FIRST_ROWS */ a1.damage
from damage A1
where a1.damage_id in
(select * from table (cast(str2tbl('LA123') as mystr2tabletype)) where rownum>=0);

This works like a charm utilizing the indexes.

But when I add UNIQUE to the mix:
SELECT /*+ FIRST_ROWS */ unique a1.damage

I can't get the indexes to be used & the consistent gets & physical reads increase dramatically.

Is there another way around this? I really don't want to loose the varying IN list capability.

Tom Kyte
December 01, 2004 - 7:41 pm UTC

</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:3779680732446#15740265481549 <code>

cardinality "knowledge" will help - first_rows is not useful with UNIQUE so much since to unique you need to get all rows.

Extremely useful, thanks

Steve Purkis, March 29, 2005 - 6:24 am UTC

I've found this explanation of how COMMA_TO_TABLE works quite handy -- I was also trying to use it as a generic split() function without realizing its true purpose.

-Steve

A reader, July 20, 2006 - 4:33 pm UTC


Table to Comma

djb, June 29, 2009 - 4:56 pm UTC

I'm in the process of taking a table and converting it to a CSV file. All is going well, but now I have an additional task:

create table product (prod_id varchar2(30) primary key, price number(10,2));

create table category_map (prod_id foreign key references product, category_name varchar2(30));

insert into product values ('MOUSE-PAD', 1.0);
insert into category_map values ('MOUSE-PAD', 'ACCESSORY');
insert into category_map values ('MOUSE-PAD', 'PLASTIC');
{just making stuff up there...}

I'm trying to get output like the following in one SQL statement if possible:

The SQL I'm envisioning is something like:

SELECT P.PROD_ID, {{magic happens here to get CATEGORY_MAP
to a single comma-delimited row}}, P.PRICE
FROM PRODUCT P
JOIN CATEGORY_MAP C
ON P.PROD_ID = C.PROD_ID

Result would look like:
"MOUSE-PAD","ACCESSORY,PLASTIC",1

I know I'm way off on the above SQL - if you can steer me in the right direction, I would be very appreciative...

Thanks!
Tom Kyte
July 06, 2009 - 6:35 pm UTC

ops$tkyte%ORA10GR2> select deptno,
  2         substr( max(sys_connect_by_path(ename,',')),2) scbp
  3    from (select deptno, ename, row_number() over (partition by deptno order by 'x') rn
  4            from scott.emp)
  5   start with rn = 1
  6   connect by prior deptno = deptno and prior rn+1 = rn
  7   group by deptno
  8  /

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


that is a technique, once you understand the technique, applying this technique to your query will be trivial.

Table to Comma

A reader, July 09, 2009 - 2:44 pm UTC

Tom, inspired by your example, I experimented with the model clause and came up with the following. Let me know what you think. Thanks.

select deptno, x
from (
 select deptno, ename
  , row_number()over(partition by deptno order by 'x') rn
 from emp
)
model
return updated rows
partition by (deptno)
dimension by (rn)
measures (ename, cast(ename as varchar2(200)) x)
rules iterate(20) until (ename[iteration_number+3] is null) (
 x[1] = x[1] ||
        nullif(',' || ename[iteration_number+2], ',')
)
;


More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library