将SELECT STATEMENT作为IN参数传递给过程并在Oracle中执行

时间:2018-10-24 13:28:06

标签: oracle select plsql parameters procedure

我有以下步骤

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;
/

我需要将select语句替换为“ something”,以便可以在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,但是没有用。

该过程应该能够处理任何选择语句。

1 个答案:

答案 0 :(得分:5)

由于您不知道编译时传入查询将返回的列,因此无法在循环中静态引用它们。

您可以使用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在注释中建议的那样,您可以使用ref游标来解析和执行查询,这应避免运行任何讨厌的东西。 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循环之前将列名称写为标题行:

   -- 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);