how to convert csv to table in oracle
Asked Answered
S

5

11

How can I make a package that returns results in table format when passed in csv values.

select * from table(schema.mypackage.myfunction('one, two, three'))

should return

one
two
three

I tried something from ask tom but that only works with sql types.

I am using oracle 11g. Is there something built-in?

Sande answered 29/6, 2010 at 16:22 Comment(0)
C
16

The following works invoke it as select * from table(splitter('a,b,c,d'))

create or replace function splitter(p_str in varchar2) return  sys.odcivarchar2list
is
v_tab sys.odcivarchar2list:=new sys.odcivarchar2list();
begin
with cte as (select level  ind from dual
connect by 
level <=regexp_count(p_str,',') +1
)
select regexp_substr(p_str,'[^,]+',1,ind)
bulk collect into v_tab
from cte;
return v_tab;
end;
/
Cotton answered 29/6, 2010 at 17:1 Comment(1)
Is the regexp_substr function parameter '[^,]+' to allow for null fields, such as '123,456,,999'?Wilful
P
5

Alas, in 11g we still have to handroll our own PL/SQL tokenizers, using SQL types. In 11gR2 Oracle gave us a aggregating function to concatenate results into a CSV string, so perhaps in 12i they will provide the reverse capability.

If you don't want to create a SQL type especially you can use the built-in SYS.DBMS_DEBUG_VC2COLL, like this:

create or replace function string_tokenizer
    (p_string in varchar2
        , p_separator in varchar2 := ',')
    return sys.dbms_debug_vc2coll
is
    return_value SYS.DBMS_DEBUG_VC2COLL;
    pattern varchar2(250);
begin

    pattern := '[^('''||p_separator||''')]+' ;

    select trim(regexp_substr (p_string, pattern, 1, level)) token
    bulk collect into return_value
    from dual
    where regexp_substr (p_string, pattern, 1, level) is not null
    connect by regexp_instr (p_string, pattern, 1, level) > 0;

    return return_value;

end string_tokenizer;
/

Here it is in action:

SQL> select * from table (string_tokenizer('one, two, three'))
  2  /

COLUMN_VALUE
----------------------------------------------------------------
one
two
three

SQL>

Acknowledgement: this code is a variant of some code I found on Tanel Poder's blog.

Purl answered 29/6, 2010 at 16:42 Comment(0)
D
5

Here is another solution using a regular expression matcher entirely in sql.

SELECT regexp_substr('one,two,three','[^,]+', 1, level) abc
FROM dual 
CONNECT BY regexp_substr('one,two,three', '[^,]+', 1, level) IS NOT NULL
Duke answered 5/12, 2012 at 4:47 Comment(0)
E
4

For optimal performance, it is best to avoid using hierarchical (CONNECT BY) queries in the splitter function.

The following splitter function performs a good deal better when applied to greater data volumes

CREATE OR REPLACE FUNCTION row2col(p_clob_text IN VARCHAR2) 
   RETURN sys.dbms_debug_vc2coll PIPELINED 
IS
     next_new_line_indx PLS_INTEGER;
     remaining_text VARCHAR2(20000);
     next_piece_for_piping VARCHAR2(20000);
  BEGIN

    remaining_text := p_clob_text;
    LOOP
       next_new_line_indx := instr(remaining_text, ','); 
       next_piece_for_piping :=
          CASE
             WHEN next_new_line_indx <> 0 THEN
                TRIM(SUBSTR(remaining_text, 1, next_new_line_indx-1))
             ELSE
                TRIM(SUBSTR(remaining_text, 1))
          END;

       remaining_text := SUBSTR(remaining_text, next_new_line_indx+1 );
       PIPE ROW(next_piece_for_piping);
       EXIT WHEN next_new_line_indx = 0 OR remaining_text IS NULL;
    END LOOP;
    RETURN;
  END row2col;
/

This performance difference can be observed below (I used the function splitter as was given earlier in this discussion).

SQL> SET TIMING ON
SQL>
SQL> WITH SRC AS (
  2  SELECT rownum||',a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'||rownum txt
  3  FROM DUAL
  4  CONNECT BY LEVEL <=10000
  5  )
  6  SELECT  NULL
  7  FROM SRC, TABLE(SYSTEM.row2col(txt)) t
  8  HAVING MAX(t.column_value) > 'zzz'
  9  ;

no rows selected

Elapsed: 00:00:00.93
SQL>
SQL> WITH SRC AS (
  2  SELECT rownum||',a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'||rownum txt
  3  FROM DUAL
  4  CONNECT BY LEVEL <=10000
  5  )
  6  SELECT  NULL
  7  FROM SRC, TABLE(splitter(txt)) t
  8  HAVING MAX(t.column_value) > 'zzz'
  9  ;

no rows selected

Elapsed: 00:00:14.90
SQL>
SQL> SET TIMING OFF
SQL>
Epideictic answered 7/7, 2014 at 13:46 Comment(0)
P
0

I don't have 11g installed to play with, but there is a PIVOT and UNPIVOT operation for converting columns to rows / rows to columns, that may be a good starting point.

http://www.oracle.com/technology/pub/articles/oracle-database-11g-top-features/11g-pivot.html

(Having actually done some further investigation, this doesn't look suitable for this case - it works with actual rows / columns, but not sets of data in a column).

There is also DBMS_UTILITY.comma_to_table and table_to_comma for converting CSV lists into pl/sql tables. There are some limitations (handling linefeeds, etc) but may be a good starting point.

My inclination would be to use the TYPE approach, with a simple function that does comma_to_table, then PIPE ROW for each entry in the result of comma_to_table (unfortunately, DBMS_UTILITY.comma_to_table is a procedure so cannot call from SQL).

Pomade answered 29/6, 2010 at 17:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.