Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Übergeben Sie SELECT STATEMENT als IN-Parameter an die Prozedur und führen Sie sie in Oracle aus

Da Sie die Spalten, die von der übergebenen Abfrage zur Kompilierzeit zurückgegeben werden, nicht kennen, können Sie innerhalb der Schleife nicht statisch auf sie verweisen.

Sie könnten den dbms_sql verwenden Paket, um dies dynamisch zu tun:

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

Das parst im Grunde die übergebene Anweisung, führt sie aus, ruft jede Zeile ab, ruft nacheinander jeden Spaltenwert ab (als Zeichenfolge, die erweitert werden könnte/sollte, um implizite Konvertierungen zu vermeiden) und schreibt jede davon in die Datei wiederum - Hinzufügen eines Trennzeichens zwischen ihnen und einer abschließenden neuen Zeile nach jeder Zeile.

Beim Aufruf aus Ihrem anonymen Block wird eine Datei erstellt, die Folgendes enthält:

NLS_CHARACTERSET;AL32UTF8
NLS_RDBMS_VERSION;11.2.0.4.0

Beachten Sie jedoch, dass dies alles ausführen wird, was angegeben ist, einschließlich DDL (das beim Analysieren ausgeführt wird). Wenn Sie nicht kontrollieren können, wie dies aufgerufen wird, und selbst wenn Sie dies tun, sollten Sie eine Validierung der übergebenen Anweisung hinzufügen, um sicherzustellen, dass es sich tatsächlich nur um eine Abfrage handelt.

Möglicherweise finden Sie es einfacher, andere Methoden zu untersuchen, z. B. externe Tabellen (wie von @Kaushik vorgeschlagen) oder Clientfunktionen.

Wie @kfinity in einem Kommentar vorgeschlagen hat, könnten Sie einen Ref-Cursor verwenden, um die Abfrage zu analysieren und auszuführen, was verhindern sollte, dass etwas Böses ausgeführt wird. Die dbms_sql Paket hat eine Funktion zum Konvertieren eines Ref-Cursors in einen nativen Cursor , verwenden Sie also diese Insetad der expliziten Schritte zum Öffnen, Analysieren und Ausführen:

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

... was dieselbe Ausgabedatei erzeugt.

Übrigens, wenn Sie wollten, könnten Sie die Spaltennamen auch als Kopfzeile vor der fetch-rows-Schleife ausschreiben:

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