thanks Connor
Tushar, March     05, 2016 - 5:21 pm UTC
 
 
I added one dbms_output line to your code but getting error. Don't understand how to process the bulk collected value
if l_row_count > 0 then
   dbms_output.put_line('Fetched '||l_row_count||' rows');
   for i in 1 .. l_colCnt loop
       dbms_sql.column_value( l_theCursor, i, l_col_array(i) );
       dbms_output.put_line('col_value'||l_col_array(i));
   end loop;
end if;
 
March     06, 2016 - 4:16 am UTC 
 
l_col_array is an array of arrays, 
so 
l_col_array(1) represents column #1
l_col_array(2) represents column #2
and so forth.
So 
l_col_array(1)(1) represents the first value fetched for column 1
l_col_array(1)(2) represents the first value fetched for column 2
l_col_array(1)(3) represents the first value fetched for column 3
etc
Hope this helps.
 
 
 
However, if you want to extract the actual data ...
Norman Dunbar, November  30, 2018 - 12:59 pm UTC
 
 
Hi Connor,
I'm late to the party again. 
I used the code in the original example to do some dynamic SQL with bulk collect. I kept getting duplicate output. Not good.
The fix is to keep a running count of rows returned so far, and offset the 'i' in DBMS_SQL.COLUMN_VALUE, as follows:
set serverout on size 999999
declare
    p_query varchar2(32767) := 'select * from emp';
    type column_array is table of DBMS_SQL.VARCHAR2_TABLE index by pls_integer;
    l_col_array column_array;
    l_empty_table DBMS_SQL.VARCHAR2_TABLE;
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(4000);
    l_status        integer;
    l_row_count     integer;
    l_descTbl       dbms_sql.desc_tab;
    l_colCnt        number;
    l_fetch_size    int := 10;
    n number := 0;
    l_total_rows    number := 0;
    
begin
    dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
    dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
    for i in 1 .. l_colCnt loop
        l_col_array(i) := l_empty_table;
        dbms_sql.define_array(l_theCursor, i, l_col_array(i), l_fetch_size, 1);
    end loop;
    l_status := dbms_sql.execute(l_theCursor);
    loop
      l_row_count := dbms_sql.fetch_rows(l_theCursor);
      if l_row_count > 0 then
        dbms_output.put_line('Fetched '||l_row_count||' rows');
        for i in 1 .. l_colCnt loop
            --l_col_array(i).delete;
            dbms_sql.column_value( l_theCursor, i, l_col_array(i) );
        end loop;
        
        -- Display the data.
        for i in 1 .. l_row_count loop
           for j in 1 .. l_colCnt loop
              -- We need to offset the row index to account for previous batches
              -- otherwise we get duplicate data. Ask me how I know!  
              dbms_output.put_line(l_col_array(j)(i + l_total_rows));
           end loop;        
        end loop;
        
      end if;
      
      exit when l_row_count < l_fetch_size;
      
      -- And adjust the row offset.
      total_rows := total_rows + l_row_count;
    end loop;
    DBMS_SQL.CLOSE_CURSOR(l_theCursor);
end;
/
(Sorry about the formatting, it got eaten by the review submission process!)
HTH
Cheers,
Norm. 
 
Formatting fixed. Apologies.
Norman Dunbar, November  30, 2018 - 1:01 pm UTC
 
 
Sorry, I can't read either. I fixed the formatting. (Sigh!)
set serverout on size 999999
declare
    p_query varchar2(32767) := 'select * from emp';
    type column_array is table of DBMS_SQL.VARCHAR2_TABLE index by pls_integer;
    l_col_array column_array;
    l_empty_table DBMS_SQL.VARCHAR2_TABLE;
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(4000);
    l_status        integer;
    l_row_count     integer;
    l_descTbl       dbms_sql.desc_tab;
    l_colCnt        number;
    l_fetch_size    int := 10;
    n number := 0;
    l_total_rows    number := 0;
    
begin
    dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
    dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
    for i in 1 .. l_colCnt loop
        l_col_array(i) := l_empty_table;
        dbms_sql.define_array(l_theCursor, i, l_col_array(i), l_fetch_size, 1);
    end loop;
    l_status := dbms_sql.execute(l_theCursor);
    loop
      l_row_count := dbms_sql.fetch_rows(l_theCursor);
      if l_row_count > 0 then
        dbms_output.put_line('Fetched '||l_row_count||' rows');
        for i in 1 .. l_colCnt loop
            --l_col_array(i).delete;
            dbms_sql.column_value( l_theCursor, i, l_col_array(i) );
        end loop;
        
        -- Display the data.
        for i in 1 .. l_row_count loop
           for j in 1 .. l_colCnt loop
              -- We need to offset the row index to account for previous batches
              -- otherwise we get duplicate data. Ask me how I know!  
              dbms_output.put_line(l_col_array(j)(i + l_total_rows));
           end loop;        
        end loop;
        
      end if;
      
      exit when l_row_count < l_fetch_size;
      
      -- And adjust the row offset.
      total_rows := total_rows + l_row_count;
    end loop;
    DBMS_SQL.CLOSE_CURSOR(l_theCursor);
end;
/
 
December  03, 2018 - 3:29 am UTC 
 
Thanks for the update.  I've replaced our original answer with yours. 
 
 
Awesome example of using dbms_sql with bulk collect
A reader, February  20, 2019 - 10:20 am UTC
 
 
Great work ..please keep doing it.
Thanks a lot.
 
February  20, 2019 - 11:51 am UTC 
 
You're welcome 
 
 
jron, June      14, 2019 - 10:03 am UTC
 
 
<more code>
loop
      l_row_count := dbms_sql.fetch_rows(l_theCursor);
      if l_row_count > 0 then
        dbms_output.put_line('Fetched '||l_row_count||' rows');
        for i in 1 .. l_colCnt loop
            --l_col_array(i).delete;
            dbms_sql.column_value( l_theCursor, i, l_col_array(i) );
        end loop;
        
        -- Display the data.
        for i in 1 .. l_row_count loop
           for j in 1 .. l_colCnt loop
              -- We need to offset the row index to account for previous batches
              -- otherwise we get duplicate data. Ask me how I know!  
              dbms_output.put_line(l_col_array(j)(i + l_total_rows));
           end loop;        
        end loop;
        
      end if;
      
      exit when l_row_count < l_fetch_size;
      
      -- And adjust the row offset.
      total_rows := total_rows + l_row_count;
    end loop;
   
   <more code>
Assuming a generic procedure/function will be created based on above code, if there are a few millions of records to be printed (e.g., in a file) then that means all records will be loaded in the array once the last batch of rows are retrieved, right? Wouldn't it be better to refresh the array before retrieving another set of records?
   <more code>
   
loop
 -- define_array in the loop
 for i in 1 .. l_colCnt loop
    l_col_array(i) := l_empty_table;
    dbms_sql.define_array(l_theCursor, i, l_col_array(i), l_fetch_size, 1);
 end loop;
 l_row_count := dbms_sql.fetch_rows(l_theCursor);
 if l_row_count > 0 then
    for i in 1 .. l_colCnt 
    loop
       dbms_sql.column_value(l_theCursor, i, l_col_array(i));
    end loop;
    -- Display the data.
    for i in 1 .. l_row_count 
    loop
       v_Separator := '';         
       for j in 1 .. l_colCnt 
       loop
          -- no need to offset row index 
          utl_file.put(v_Separator || l_col_array(j)(i));
          v_Separator := p_Separator;
       end loop;
       utl_file.put_line('');
       l_total_rows := l_total_rows + l_row_count;
    end loop;
 end if;
 exit when l_row_count < l_fetch_size;
end loop;
   
   <more code>
 
 
June      18, 2019 - 3:22 am UTC 
 
Agreed. Depends on what you want to achieve here, ie, get the entire result set, or loop through it and discard processed data.