Ok ballboas, realmente tinha mal compreendido a questão.
Realmente se faz necessário o uso do "DBMS_SQL" nesses caso.
Aproveitando o seu trecho de código segue o seguinte que informa o número de colunas, os nomes delas, e o resultado. Tudo como se fosse um CSV, separado por ponto e vírgula.
Selecionar tudo
DECLARE
v_cursor NUMBER;
v_col_cnt INTEGER;
rec_tab dbms_sql.desc_tab;
p_sql LONG;
v_ind NUMBER;
v_line VARCHAR2(250);
v_header VARCHAR2(250);
v_result VARCHAR2(4000);
BEGIN
p_sql := 'select (sysdate) hoje,
(sysdate + 1) amanha,
1 um,
2 dois
from dual
UNION ALL
select (sysdate - 23) hoje,
(sysdate + 19) amanha,
71 um,
62 dois
from dual ';
dbms_output.enable(NULL); --infinito
v_cursor := dbms_sql.open_cursor;
dbms_sql.parse(v_cursor, p_sql, dbms_sql.native);
dbms_sql.describe_columns(v_cursor, v_col_cnt, rec_tab);
--Printa a quantidade de colunas
dbms_output.put_line('***TOTAL COLUNAS***:' || v_col_cnt);
--Loop com a quantidade de colunas printando o nome de cada coluna
for n in 1..v_col_cnt loop
v_header := v_header || rec_tab(n).col_name || ';';
end loop;
--Exibe cabeçalho
v_header := substr(v_header, 1, length(v_header) -1);
dbms_output.put_line(v_header);
/*** DEFINE COLUNAS ***/
FOR v_pos IN 1 .. rec_tab.LAST
LOOP
v_line := rec_tab(v_pos).col_name;
dbms_sql.define_column(v_cursor, v_pos, v_line, 250);
END LOOP;
/* Loop nas linhas de retorno da consulta */
v_ind := dbms_sql.EXECUTE(v_cursor);
LOOP
v_ind := dbms_sql.fetch_rows(v_cursor);
EXIT WHEN v_ind = 0;
/* loop para cada coluna da linha */
FOR v_col_seq IN 1 .. rec_tab.COUNT
LOOP
-- PEGA O VALOR DA COLUNA
dbms_sql.column_value(v_cursor, v_col_seq, v_line);
IF v_col_seq = 1 THEN
v_result := v_line;
ELSE
v_result := v_result || ';' || v_line;
END IF;
END LOOP;
--EXIBE CADA LINHA
dbms_output.put_line(v_result);
END LOOP;
END;