将 SELECT STATEMENT 作为 IN 参数传递给过程并在 Oracle 中执行
Pass SELECT STATEMENT as IN parameter to procedure and execute in Oracle
我有以下程序
CREATE OR REPLACE PROCEDURE p_create_text_file (
loc IN VARCHAR2
, file IN VARCHAR2
, select_statement in varchar2
, line_statement in varchar2
)
IS
fid UTL_FILE.FILE_TYPE := UTL_FILE.FOPEN (loc, file, 'W');
line VARCHAR2(2000);
BEGIN
FOR rec IN (
/*replace this select*/
select
parameter
, value
from nls_database_parameters
where parameter in ('NLS_RDBMS_VERSION', 'NLS_CHARACTERSET')
/*end of replace*/
)
LOOP
line := rec.parameter || ';' || rec.value;
UTL_FILE.PUT_LINE (fid, line);
END LOOP;
UTL_FILE.FCLOSE (fid);
EXCEPTION
WHEN OTHERS THEN UTL_FILE.FCLOSE (fid);
END;
/
我需要用 "something" 替换 select 语句,以便它可以在 IN 参数中传递 SELECT_STATEMENT.
过程调用应如下所示:
begin
p_create_text_file (
loc => 'EXPDIR'
, file => 'exp.log'
, select_statement => 'select parameter, value from nls_database_parameters where parameter in (''NLS_RDBMS_VERSION'', ''NLS_CHARACTERSET'')'
, line_statement => null
);
end;
/
我尝试了动态 SQL 但它没有用。
该过程应该能够处理任何 select 语句。
由于您不知道 passed-in 查询在编译时返回的列,因此您不能在循环内静态引用它们。
您可以使用 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;
/
基本上,解析 passed-in 语句,执行它,获取每一行,依次获取每一列值(作为字符串,could/should 被扩展以避免隐式转换),并依次将其中的每一个写入文件 - 在它们之间添加一个分隔符,并在每行之后添加一个最后的 new-line。
当从创建包含以下内容的文件的匿名块调用时:
NLS_CHARACTERSET;AL32UTF8
NLS_RDBMS_VERSION;11.2.0.4.0
请注意,thoguh,这将 运行 任何给定的内容,包括 DDL(在解析时执行)。如果您不控制它的调用方式,即使您确实控制了它,您也应该添加 passed-in 语句的验证以验证它实际上只是一个查询。
您可能会发现探索其他方法更简单,例如外部表(如@Kaushik 建议的那样)或客户端功能。
正如@kfinity 在评论中建议的那样,您可以使用引用游标来解析和执行查询,这应该可以防止任何令人讨厌的事情发生 运行。 dbms_sql
包 has a function to convert a ref cursor to a native cursor,因此使用显式打开、解析和执行步骤的插图:
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;
/
...生成相同的输出文件。
顺便说一句,如果您愿意,也可以在 fetch-rows 循环之前将列名写成 header 行:
-- 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);
我有以下程序
CREATE OR REPLACE PROCEDURE p_create_text_file (
loc IN VARCHAR2
, file IN VARCHAR2
, select_statement in varchar2
, line_statement in varchar2
)
IS
fid UTL_FILE.FILE_TYPE := UTL_FILE.FOPEN (loc, file, 'W');
line VARCHAR2(2000);
BEGIN
FOR rec IN (
/*replace this select*/
select
parameter
, value
from nls_database_parameters
where parameter in ('NLS_RDBMS_VERSION', 'NLS_CHARACTERSET')
/*end of replace*/
)
LOOP
line := rec.parameter || ';' || rec.value;
UTL_FILE.PUT_LINE (fid, line);
END LOOP;
UTL_FILE.FCLOSE (fid);
EXCEPTION
WHEN OTHERS THEN UTL_FILE.FCLOSE (fid);
END;
/
我需要用 "something" 替换 select 语句,以便它可以在 IN 参数中传递 SELECT_STATEMENT.
过程调用应如下所示:
begin
p_create_text_file (
loc => 'EXPDIR'
, file => 'exp.log'
, select_statement => 'select parameter, value from nls_database_parameters where parameter in (''NLS_RDBMS_VERSION'', ''NLS_CHARACTERSET'')'
, line_statement => null
);
end;
/
我尝试了动态 SQL 但它没有用。
该过程应该能够处理任何 select 语句。
由于您不知道 passed-in 查询在编译时返回的列,因此您不能在循环内静态引用它们。
您可以使用 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;
/
基本上,解析 passed-in 语句,执行它,获取每一行,依次获取每一列值(作为字符串,could/should 被扩展以避免隐式转换),并依次将其中的每一个写入文件 - 在它们之间添加一个分隔符,并在每行之后添加一个最后的 new-line。
当从创建包含以下内容的文件的匿名块调用时:
NLS_CHARACTERSET;AL32UTF8
NLS_RDBMS_VERSION;11.2.0.4.0
请注意,thoguh,这将 运行 任何给定的内容,包括 DDL(在解析时执行)。如果您不控制它的调用方式,即使您确实控制了它,您也应该添加 passed-in 语句的验证以验证它实际上只是一个查询。
您可能会发现探索其他方法更简单,例如外部表(如@Kaushik 建议的那样)或客户端功能。
正如@kfinity 在评论中建议的那样,您可以使用引用游标来解析和执行查询,这应该可以防止任何令人讨厌的事情发生 运行。 dbms_sql
包 has a function to convert a ref cursor to a native cursor,因此使用显式打开、解析和执行步骤的插图:
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;
/
...生成相同的输出文件。
顺便说一句,如果您愿意,也可以在 fetch-rows 循环之前将列名写成 header 行:
-- 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);