Тъй като не знаете колоните, които ще бъдат върнати от предадената заявка по време на компилиране, не можете да ги препращате вътре в цикъла статично.
Можете да използвате dbms_sql
пакет, за да направите това динамично:
CREATE OR REPLACE PROCEDURE p_create_text_file (
loc IN VARCHAR2
, file IN VARCHAR2
, select_statement in varchar2
, line_statement in varchar2 -- not used?
)
IS
fid UTL_FILE.FILE_TYPE := UTL_FILE.FOPEN (loc, file, 'W');
-- for dbms_sql
l_c pls_integer;
l_col_cnt pls_integer;
l_desc_t dbms_sql.desc_tab3;
l_rc pls_integer;
l_varchar varchar2(4000);
BEGIN
-- create cursor and prepare from passed-in statement
l_c := dbms_sql.open_cursor;
dbms_sql.parse(c=>l_c, statement=>select_statement,
language_flag=>dbms_sql.native);
dbms_sql.describe_columns3(c => l_c, col_cnt => l_col_cnt,
desc_t => l_desc_t);
-- define all columns as strings; this will end up with implicit conversion
-- of dates etc. using NLS settings, so shoudl be finsessed based on data
-- actual data type really...
for i in 1..l_col_cnt loop
dbms_sql.define_column(c=>l_c, position=>i,
column=>l_varchar, column_size=>4000);
end loop;
-- execute the query
l_rc := dbms_sql.execute(c=>l_c);
-- fetch each row in turn
while dbms_sql.fetch_rows(c=>l_c) > 0 loop
-- for each column from describe
for i in 1..l_col_cnt loop
-- get the column value for this row (again, as string...)
dbms_sql.column_value(l_c, i, l_varchar);
-- write out to file, with delimiter after first column
if i > 1 then
UTL_FILE.PUT (fid, ';');
end if;
UTL_FILE.PUT (fid, l_varchar);
end loop;
UTL_FILE.NEW_LINE (fid);
end loop;
dbms_sql.close_cursor(l_c);
UTL_FILE.FCLOSE (fid);
EXCEPTION
WHEN OTHERS THEN UTL_FILE.FCLOSE (fid);
END;
/
Това основно анализира предадения оператор, изпълнява го, извлича всеки ред, получава всяка стойност на колона на свой ред (като низ, който може/трябва да бъде разширен, за да се избегнат имплицитни преобразувания) и записва всяка от тях във файла на свой ред - добавяне на разделител между тях и последен нов ред след всеки ред.
При извикване от вашия анонимен блок, който създава файл, съдържащ:
NLS_CHARACTERSET;AL32UTF8
NLS_RDBMS_VERSION;11.2.0.4.0
Имайте предвид, че това ще изпълнява всичко, което е дадено, включително DDL (който се изпълнява, когато се анализира). Ако не контролирате как се извиква това и наистина дори да го правите, трябва да добавите валидиране на предадения оператор, за да проверите дали всъщност е само заявка.
Може да откриете, че е по-лесно да изследвате други методи, като външни таблици (както предложи @Kaushik) или клиентска функционалност.
Както @kfinity предложи в коментар, можете да използвате референтен курсор, за да анализирате и изпълните заявката, което трябва да предотврати стартирането на нещо неприятно. dbms_sql
пакет има функция за преобразуване на референтен курсор в оригинален курсор
, така че използвайки този insetad на изричните стъпки за отваряне, анализ и изпълнение:
CREATE OR REPLACE PROCEDURE p_create_text_file (
loc IN VARCHAR2
, file IN VARCHAR2
, select_statement in varchar2
, line_statement in varchar2 -- not used?
)
IS
fid UTL_FILE.FILE_TYPE := UTL_FILE.FOPEN (loc, file, 'W');
-- for initial parse and execute
l_refcursor sys_refcursor;
-- for dbms_sql
l_c pls_integer;
l_col_cnt pls_integer;
l_desc_t dbms_sql.desc_tab3;
l_rc pls_integer;
l_varchar varchar2(4000);
BEGIN
-- open ref cursor for the statement
open l_refcursor for select_statement;
-- convert ref cursor to dbms_sql cursor
l_c := dbms_sql.to_cursor_number(l_refcursor);
dbms_sql.describe_columns3(c => l_c, col_cnt => l_col_cnt,
desc_t => l_desc_t);
-- define all columns as strings; this will end up with implicit conversion
-- of dates etc. using NLS settings, so shoudl be finsessed based on data
-- actual data type really...
for i in 1..l_col_cnt loop
dbms_sql.define_column(c=>l_c, position=>i,
column=>l_varchar, column_size=>4000);
end loop;
-- fetch each row in turn
while dbms_sql.fetch_rows(c=>l_c) > 0 loop
-- for each column from describe
for i in 1..l_col_cnt loop
-- get the column value for this row (again, as string...)
dbms_sql.column_value(l_c, i, l_varchar);
-- write out to file, with delimiter after first column
if i > 1 then
UTL_FILE.PUT (fid, ';');
end if;
UTL_FILE.PUT (fid, l_varchar);
end loop;
UTL_FILE.NEW_LINE (fid);
end loop;
dbms_sql.close_cursor(l_c);
UTL_FILE.FCLOSE (fid);
EXCEPTION
WHEN OTHERS THEN UTL_FILE.FCLOSE (fid);
END;
/
... което произвежда същия изходен файл.
Между другото, ако искате, можете също да напишете имената на колоните като заглавен ред, преди цикъла за извличане на редове:
-- write column names as header row
for i in 1..l_col_cnt loop
if i > 1 then
UTL_FILE.PUT (fid, ';');
end if;
UTL_FILE.PUT (fid, l_desc_t(i).col_name);
end loop;
UTL_FILE.NEW_LINE (fid);