DECLARE
l_sql VARCHAR2(32767):='
SELECT 1 field_11, ''river'' field_22 FROM DUAL
UNION ALL
SELECT 1 field_11, ''sharf'' field_22 FROM DUAL
';
l_cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR;
l_ind PLS_INTEGER;
l_col_count PLS_INTEGER;
l_col_names DBMS_SQL.DESC_TAB;
l_out VARCHAR2(32767);
col_val_chr VARCHAR2(32767);
LNBR VARCHAR2(2) := CHR(13)||CHR(10);
col_delim VARCHAR2(20) := '</td><td>';
row_delim VARCHAR2(20) := '</td></tr>'|| LNBR ||'<tr><td>';
BEGIN
-- Разбор запроса
DBMS_SQL.PARSE (
l_cur,
l_sql,
DBMS_SQL.NATIVE
);
-- Получение информации о столбцах
DBMS_SQL.DESCRIBE_COLUMNS (l_cur, l_col_count, l_col_names);
-- Вывод каждого из имен столбцов
FOR col_ind IN 1 .. l_col_count
LOOP
-- Определим тип столбца
DBMS_SQL.DEFINE_COLUMN(l_cur, col_ind, col_val_chr, 32767);
-- Выводим наимнование столбца
l_out := l_out || l_col_names(col_ind).col_name;
-- Если не последний эл. вставляем разделитель
IF col_ind != l_col_count
THEN
l_out := l_out || col_delim;
END IF;
END LOOP;
-- Выполняем и фетчим курсор
l_ind := DBMS_SQL.EXECUTE (l_cur);
LOOP
l_ind := DBMS_SQL.FETCH_ROWS( l_cur );
EXIT WHEN l_ind = 0;
l_out := l_out || row_delim;
FOR col_ind IN 1 .. l_col_count
LOOP
-- Читаем и выводим значение
DBMS_SQL.COLUMN_VALUE (l_cur, col_ind, col_val_chr);
l_out := l_out || col_val_chr;
-- Если не последний эл. вставляем разделитель
IF col_ind != l_col_count
THEN
l_out := l_out || col_delim;
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR (l_cur);
DBMS_OUTPUT.PUT_LINE('<table>'||LNBR||'<tr><td>'|| l_out ||'</td></tr>'||LNBR||'</table>');
END;
/
/*
-- Если задать разделители полей ; и LNBR то на выходе получим CSV данные
col_delim VARCHAR2(20) := ';';
row_delim VARCHAR2(20) := LNBR;
*/
см. пример на dbfiddle
PS: Вы видимо что-то недочитали :)
Спасибо, сам разобрался, даже и не знал что так можно, теперь наверно буду использовать :)
UPDATE:
Можно обернуть в функцию и помимо прочего передавать в нее тип разделителя (для HTML можно передавать стили или classname для таблицы) и на выходе иметь разный формат данных.