Home » SQL & PL/SQL » SQL & PL/SQL » Polymorphic Table Function Issue (Oracle, 19.2, Linux)
Polymorphic Table Function Issue [message #690473] Wed, 08 April 2026 11:14 Go to next message
Duane
Messages: 591
Registered: December 2002
Senior Member
I have a working example but I need some clarification on a few items I'm not understanding.

ISSUE:  I want "column1" to be part of the select statement but not the row being parsed.  I want the first parsed element of the data.

SOLUTION: "column1" should contain the value of "one" and not "one, two, three, four, five".  All other columns should contain the correct value.
         "column1" should contain the value of "six" and not "six, seven, eight, nine, ten". All other columns should contain the correct value.

Row 1
column1 = one
column2 = two
column3 = three
column4 = four
column5 = five

Row 2
column1 = six
column2 = seven
column3 = eight
column4 = nine
column5 = ten


Package Spec

create or replace package CSVData as  

  function Describe (TableName    in out dbms_tf.table_t,
                     ColumnNumber in number   default 100,
                     Delimiter    in varchar2 default ',') return dbms_tf.describe_t;


  procedure fetch_rows (ColumnNumber in number   default 100,
                        Delimiter    in varchar2 default ',');  

end CSVData;
Package Body

create or replace package body CSVData as  

  function Describe (TableName    in out dbms_tf.table_t, 
                     ColumnNumber in number   default 100,
                     Delimiter    in varchar2 default ',') return dbms_tf.describe_t as  
    
    ColumnNew dbms_tf.columns_new_t;


    begin
      TableName.column(1).pass_through := false;  
      TableName.column(1).for_read := true;
      
      for i in 1..ColumnNumber
        loop   
          ColumnNew(i) := dbms_tf.column_metadata_t(name => 'column'||i, type => dbms_tf.type_varchar2); 
        end loop;  

      return dbms_tf.describe_t(new_columns => ColumnNew);  
    end;  


  procedure fetch_rows (ColumnNumber in number   default 100,
                        Delimiter    in varchar2 default ',') as   

    RowCount pls_integer;
    RowSet   dbms_tf.row_set_t;


    begin  
      dbms_tf.get_row_set(RowSet, RowCount);  
      
      for i in 1..RowCount 
        loop
          for j in 2..ColumnNumber
            loop  
              RowSet(j).tab_varchar2(i) := regexp_substr(RowSet(1).tab_varchar2(i), '[^'||Delimiter||']+', 1, j - 1);
            end loop;
        end loop;

      dbms_tf.put_row_set(RowSet);  
    end;  

end CSVData;
Function

create or replace function CSVColumn(TableName    in table, 
                                     ColumnNumber in number   default 100,
                                     Delimiter    in varchar2 default ',') return table pipelined row polymorphic using CSVData;
Test Data

with 
  csvs as (  
    select 'one, two, three, four, five' str from dual
    union
    select 'six, seven, eight, nine, ten' str from dual 
  )
  
select *   
  from CSVColumn(csvs, 10);
Within "fetch_rows" I don't quite understand why "for j in 2..ColumnNumber" has to start with 2 (for j in 2..ColumnNumber j - 1).

for i in 1..RowCount 
  loop
    for j in 2..ColumnNumber
      loop  
        RowSet(j).tab_varchar2(i) := regexp_substr(RowSet(1).tab_varchar2(i), '[^'||Delimiter||']+', 1, j - 1);
    end loop;
  end loop;

If I replace 2 with 1 and remove -1 from J then I get column1 = "one" or column1 = "six" but all the other columns are null (for j in 1..ColumnNumber j ).

for i in 1..RowCount 
  loop
    for j in 1..ColumnNumber
      loop  
        RowSet(j).tab_varchar2(i) := regexp_substr(RowSet(1).tab_varchar2(i), '[^'||Delimiter||']+', 1, j);
    end loop;
  end loop;

[Updated on: Wed, 08 April 2026 11:18]

Report message to a moderator

Re: Polymorphic Table Function Issue [message #690474 is a reply to message #690473] Thu, 09 April 2026 10:56 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3310
Registered: January 2010
Location: Connecticut, USA
Senior Member
create or replace package body CSVData as  
  function Describe (TableName    in out dbms_tf.table_t, 
                     ColumnNumber in number   default 100,
                     Delimiter    in varchar2 default ',') return dbms_tf.describe_t as  
    ColumnNew dbms_tf.columns_new_t;
    begin
      TableName.column(1).pass_through := false;  
      TableName.column(1).for_read := true;
      for i in 1..ColumnNumber
        loop   
          ColumnNew(i) := dbms_tf.column_metadata_t(name => 'column'||i, type => dbms_tf.type_varchar2); 
        end loop;  
      return dbms_tf.describe_t(new_columns => ColumnNew);  
    end;  
  procedure fetch_rows (ColumnNumber in number   default 100,
                        Delimiter    in varchar2 default ',') as   
    RowCount pls_integer;
    RowSet   dbms_tf.row_set_t;
    OriginalValue varchar2(4000);
    begin  
      dbms_tf.get_row_set(RowSet, RowCount);  
      for i in 1..RowCount
        loop
          OriginalValue := RowSet(1).tab_varchar2(i);
          for j in 1..ColumnNumber
            loop  
              RowSet(j).tab_varchar2(i) := regexp_substr(OriginalValue, '[^'||Delimiter||']+', 1, j);
            end loop;
        end loop;
      dbms_tf.put_row_set(RowSet);  
    end;  
end CSVData;
/
Now:

SQL> create or replace package body CSVData as
  2    function Describe (TableName    in out dbms_tf.table_t,
  3                       ColumnNumber in number   default 100,
  4                       Delimiter    in varchar2 default ',') return dbms_tf.describe_t as
  5      ColumnNew dbms_tf.columns_new_t;
  6      begin
  7        TableName.column(1).pass_through := false;
  8        TableName.column(1).for_read := true;
  9        for i in 1..ColumnNumber
 10          loop
 11            ColumnNew(i) := dbms_tf.column_metadata_t(name => 'column'||i, type => dbms_tf.type_varchar2);
 12          end loop;
 13        return dbms_tf.describe_t(new_columns => ColumnNew);
 14      end;
 15    procedure fetch_rows (ColumnNumber in number   default 100,
 16                          Delimiter    in varchar2 default ',') as
 17      RowCount pls_integer;
 18      RowSet   dbms_tf.row_set_t;
 19      OriginalValue varchar2(4000);
 20      begin
 21        dbms_tf.get_row_set(RowSet, RowCount);
 22        for i in 1..RowCount
 23          loop
 24            OriginalValue := RowSet(1).tab_varchar2(i);
 25            for j in 1..ColumnNumber
 26              loop
 27                RowSet(j).tab_varchar2(i) := regexp_substr(OriginalValue, '[^'||Delimiter||']+', 1, j);
 28              end loop;
 29          end loop;
 30        dbms_tf.put_row_set(RowSet);
 31      end;
 32  end CSVData;
 33  /

Package body created.

SQL> with
  2    csvs as (
  3      select 'one, two, three, four, five' str from dual
  4      union
  5      select 'six, seven, eight, nine, ten' str from dual
  6    )
  7  select *
  8    from CSVColumn(csvs, 10);

COLUMN1 COLUMN2 COLUMN3 COLUMN4 COLUMN5 COLUMN6 COLUMN7 COLUMN8 COLUMN9 COLUMN10
------- ------- ------- ------- ------- ------- ------- ------- ------- --------
one      two     three   four    five
six      seven   eight   nine    ten

SQL>
SY.
Re: Polymorphic Table Function Issue [message #690475 is a reply to message #690474] Thu, 09 April 2026 19:30 Go to previous messageGo to next message
Duane
Messages: 591
Registered: December 2002
Senior Member
I thought for sure I had responded to this post.  Must have not hit the post button.

Anyway, thanks for clearing up my issue.  I had something very similar to what you posted but I never could get it to work like I wanted.  Doesn't matter now as you fixed it.

Thanks again.  I was wondering if you were going to take a look at what I was trying to accomplish.
Re: Polymorphic Table Function Issue [message #690476 is a reply to message #690475] Fri, 10 April 2026 06:29 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3310
Registered: January 2010
Location: Connecticut, USA
Senior Member
Actually, there is no need for Original Value. We just need to assign COLUMN1 last:

create or replace package body CSVData as
  function Describe (TableName    in out dbms_tf.table_t,
                     ColumnNumber in number   default 100,
                     Delimiter    in varchar2 default ',') return dbms_tf.describe_t as
    ColumnNew dbms_tf.columns_new_t;
    begin
      TableName.column(1).pass_through := false;
      TableName.column(1).for_read := true;
      for i in 1..ColumnNumber
        loop
          ColumnNew(i) := dbms_tf.column_metadata_t(name => 'column'||i, type => dbms_tf.type_varchar2);
        end loop;
      return dbms_tf.describe_t(new_columns => ColumnNew);
    end;
  procedure fetch_rows (ColumnNumber in number   default 100,
                        Delimiter    in varchar2 default ',') as
    RowCount pls_integer;
    RowSet   dbms_tf.row_set_t;
    begin
      dbms_tf.get_row_set(RowSet, RowCount);
      for i in 1..RowCount
        loop
          for j in 2..ColumnNumber
            loop
              RowSet(j).tab_varchar2(i) := regexp_substr(RowSet(1).tab_varchar2(i), '[^'||Delimiter||']+', 1, j);
            end loop;
         RowSet(1).tab_varchar2(i) := regexp_substr(RowSet(1).tab_varchar2(i), '[^'||Delimiter||']+');
        end loop;
      dbms_tf.put_row_set(RowSet);
    end;
end CSVData;
/

Package body created.

SQL> with
  2    csvs as (
  3      select 'one, two, three, four, five' str from dual
  4      union
  5      select 'six, seven, eight, nine, ten' str from dual
  6    )
  7  select *
  8    from CSVColumn(csvs, 10);

COLUMN1  COLUMN2  COLUMN3  COLUMN4  COLUMN5  COLUMN6  COLUMN7  COLUMN8  COLUMN9  COLUMN10
-------- -------- -------- -------- -------- -------- -------- -------- -------- --------
one       two      three    four     five
six       seven    eight    nine     ten

SQL>
SY.
Re: Polymorphic Table Function Issue [message #690477 is a reply to message #690476] Fri, 10 April 2026 09:35 Go to previous messageGo to next message
Duane
Messages: 591
Registered: December 2002
Senior Member
Ah, so that's why I never could get this to return correctly.  I kept getting my delimited data as a column.

By chance, do you know of a regular expression that will parse data correctly even when that data contains a delimiter with the text?



What I have if some columns are null:

Not based on your updated code.

RowSet(j).tab_varchar2(i) := regexp_substr(DelmitedData, '([^'||Delimiter||']*)('||Delimiter||'|$)', 1, j, null, 1);


with
      csvs as (
        select 'one, two, three,, four, five' str from dual
        union
        select 'six, seven, eight, x, nine, ten' str from dual
      )
    select *
      from CSVColumn(csvs, 10);


I did find this regular expression but it returns all nulls

,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)

I'm trying to get this to work with a delimiter within text without it breaking on the delimiter

with
      csvs as (
        select '"one", "two", "three", "this has a comma, in the text", "four", "five"' str from dual
    union
    select '"six", "seven", "eight", "x", "nine", "ten"' str from dual
      )
    select *
      from CSVColumn(csvs, 10);


"one"	 "two"	 "three"	 "this has a comma	 in the text"	 "four"	 "five"
"six"	 "seven" "eight"	 "x"	                 "nine"	         "ten"	

Re: Polymorphic Table Function Issue [message #690478 is a reply to message #690477] Fri, 10 April 2026 15:11 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3310
Registered: January 2010
Location: Connecticut, USA
Senior Member
with t as (select 'one, two, three,, four, five' str from dual)
select  rownum,
        regexp_substr(str,'([^,]*)(,|$)',1,level,null,1) x
  from  t
  connect by level <= regexp_count(str,',') + 1
/

    ROWNUM X
---------- ----------
         1 one
         2  two
         3  three
         4
         5  four
         6  five

6 rows selected.

SQL>
SY.
Re: Polymorphic Table Function Issue [message #690479 is a reply to message #690478] Fri, 10 April 2026 15:47 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3310
Registered: January 2010
Location: Connecticut, USA
Senior Member
And if you want to get rid of leading space:

with t as (select 'one, two, three,, four, five' str from dual)
select  rownum,
        regexp_substr(str,' ?([^,]*)(,|$)',1,level,null,1) x
  from  t
  connect by level <= regexp_count(str,',') + 1
/

    ROWNUM X
---------- ----------
         1 one
         2 two
         3 three
         4
         5 four
         6 five

6 rows selected.

SQL>
SY.
Re: Polymorphic Table Function Issue [message #690480 is a reply to message #690479] Fri, 10 April 2026 17:15 Go to previous messageGo to next message
Duane
Messages: 591
Registered: December 2002
Senior Member
I'm trying to parse something with an embedded comma.

As such:

[code]
with t as (select '"one", "two", "three","some comma, in the middle", "four", "five"' str from dual)
select  rownum,
       regexp_substr(str,' ?([^,]*)(,|$)',1,level,null,1) x
 from  t
 connect by level <= regexp_count(str,',') + 1

Output: Should only be six lines of output and not seven.  This text should be within one column "some comma, in the middle" and not broken into two columns.

"one"
"two"
"three"
"some comma
in the middle"
"four"
"five"



I’m trying to anticipate someone sending me data that has a delimiter within the data.

Like this:

Data:
“Account Number”, “Total”, “Full Name”, “Balance”
1234, 10000, “Presley, Elivs”, 20000
5678, 1000, “Dean, James”, 10000
9102, 5000, “Monroe, Marilyn”, 50000


Output:
column1  column2  column3         column4
1234     10000    Presley, Elivs  20000
5678     1000     Dean, James     10000
9102     5000     Monroe, Marilyn 50000

[Updated on: Fri, 10 April 2026 17:34]

Report message to a moderator

Re: Polymorphic Table Function Issue [message #690481 is a reply to message #690480] Sat, 11 April 2026 05:47 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3310
Registered: January 2010
Location: Connecticut, USA
Senior Member
with t as (select '"one", "two", "three","some comma, in the middle",, "four","", "five"' str from dual)
select  rownum,
       regexp_substr(str,' ?(("[^"]*")|([^,]*))(,|$)',1,level,null,1) x
 from  t
 connect by level <= regexp_count(regexp_replace(str,'"[^"]*"'),',') + 1
/

    ROWNUM X
---------- ----------------------------------------
         1 "one"
         2 "two"
         3 "three"
         4 "some comma, in the middle"
         5
         6 "four"
         7 ""
         8 "five"

8 rows selected.

SQL>
Now about "I’m trying to anticipate someone sending me data that has a delimiter within the data". We need rows numbered to skip headings. Next question is do we know number of result columns upfront. I'll assume we do, otherwise it will require dynamic SQL. Then:

with data as (
              select 1 rn,'"Account Number", "Total", "Full Name", "Balance"' str from dual union all
              select 2,'1234, 10000, "Presley, Elivs", 20000' from dual union all
              select 3,'5678, 1000, "Dean, James", 10000' from dual union all
              select 4,'9102, 5000, "Monroe, Marilyn", 50000' from dual
             )
select  regexp_substr(str,' ?(("[^"]*")|([^,]*))(,|$)',1,1,null,1) column1,
        regexp_substr(str,' ?(("[^"]*")|([^,]*))(,|$)',1,2,null,1) column2,
        regexp_substr(str,' ?(("[^"]*")|([^,]*))(,|$)',1,3,null,1) column3,
        regexp_substr(str,' ?(("[^"]*")|([^,]*))(,|$)',1,4,null,1) column4
  from  data
  where rn > 1
  order by rn
/

COLUMN1              COLUMN2              COLUMN3              COLUMN4
-------------------- -------------------- -------------------- --------------------
1234                 10000                "Presley, Elivs"     20000
5678                 1000                 "Dean, James"        10000
9102                 5000                 "Monroe, Marilyn"    50000

SQL>
SY.
Re: Polymorphic Table Function Issue [message #690482 is a reply to message #690481] Sat, 11 April 2026 11:20 Go to previous message
Duane
Messages: 591
Registered: December 2002
Senior Member
Thank you.

I don't need quotations around the data in Column3 so I just used an Oracle replace function to remove them.  I tried doing it in the regular expression but couldn't figure out what I needed to remove.

Thanks again for your help.
Previous Topic: Assertion error ora-8697
Next Topic: Row generator
Goto Forum:
  


Current Time: Thu Apr 16 20:37:34 CDT 2026