sql >> Databáze >  >> RDS >> Oracle

Předejte SELECT STATEMENT jako parametr IN do procedury a spusťte v Oracle

Protože neznáte sloupce, které budou vráceny předaným dotazem v době kompilace, nemůžete na ně uvnitř smyčky staticky odkazovat.

Můžete použít dbms_sql balíček, který to provede dynamicky:

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

To v podstatě analyzuje předaný příkaz, provede jej, načte každý řádek, získá postupně hodnotu každého sloupce (jako řetězec, který by mohl/měl by být rozšířen, aby se zabránilo implicitním konverzím) a zapíše všechny z nich do souboru. postupně – přidáním oddělovače mezi ně a posledního nového řádku za každý řádek.

Při volání z vašeho anonymního bloku vytvoří soubor obsahující:

NLS_CHARACTERSET;AL32UTF8
NLS_RDBMS_VERSION;11.2.0.4.0

Uvědomte si však, že toto spustí cokoli, co je zadáno, včetně DDL (které se spustí při analýze). Pokud nemáte kontrolu nad tím, jak se to nazývá, a dokonce i když ano, měli byste přidat ověření předávaného příkazu, abyste si ověřili, že je to vlastně jen dotaz.

Možná bude pro vás jednodušší prozkoumat jiné metody, jako jsou externí tabulky (jak navrhl @Kaushik) nebo funkce klienta.

Jak @kfinity navrhl v komentáři, můžete použít refkurzor k analýze a provedení dotazu, což by mělo zabránit spuštění čehokoli ošklivého. dbms_sql balíček má funkci pro převod referenčního kurzoru na nativní kurzor , takže pomocí tohoto insetadu explicitního otevření, analýzy a provedení kroků:

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

... který vytvoří stejný výstupní soubor.

Mimochodem, pokud byste chtěli, mohli byste také napsat názvy sloupců jako řádek záhlaví před smyčku 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);



  1. Jak vyladit aplikaci Ruby on Rails běžící na Heroku, která využívá produkční úroveň Heroku Postgres?

  2. Rozdělit obrovské (95Mb) pole JSON na menší části?

  3. Jak vyřešit org.postgresql.jdbc.PgConnection.createClob() ještě není implementováno

  4. Jak vložíte velké bloby do Oracle 10G pomocí System.Data.OracleClient?