Передайте SELECT STATEMENT в качестве параметра IN для процедуры и выполните в Oracle - PullRequest
0 голосов
/ 24 октября 2018

У меня есть следующая процедура

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

, и мне нужно заменить оператор выбора на «что-то», чтобы его можно было передать в параметре 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 голосов
/ 24 октября 2018

Поскольку вы не знаете столбцы, которые будут возвращены переданным запросом во время компиляции, вы не можете ссылаться на них внутри цикла статически.

Вы можете использовать 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 есть функция для преобразования курсора ссылки в собственный курсор , поэтому с помощью этой явной операции явного открытия, разбора и выполнения шагов:

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-row:

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