sql >> Database >  >> RDS >> Oracle

Geef SELECT STATEMENT als IN-parameter door aan de procedure en voer het uit in Oracle

Omdat u de kolommen niet kent die worden geretourneerd door de doorgegeven query tijdens het compileren, kunt u er niet statisch naar verwijzen in de lus.

Je zou de dbms_sql . kunnen gebruiken pakket om dit dynamisch te doen:

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

Dat, in feite, de doorgegeven instructie ontleedt, deze uitvoert, elke rij ophaalt, elke kolomwaarde op zijn beurt krijgt (als een tekenreeks, die kan/moet worden uitgebreid om impliciete conversies te voorkomen), en elk daarvan naar het bestand schrijft op zijn beurt - een scheidingsteken ertussen en een laatste nieuwe regel na elke rij.

Wanneer aangeroepen vanuit uw anonieme blok dat een bestand aanmaakt met:

NLS_CHARACTERSET;AL32UTF8
NLS_RDBMS_VERSION;11.2.0.4.0

Houd er rekening mee dat dit alles zal uitvoeren wat het wordt gegeven, inclusief DDL (dat wordt uitgevoerd wanneer het wordt geparseerd). Als je geen controle hebt over hoe dit wordt aangeroepen, en zelfs als je dat wel doet, moet je een validatie van de doorgegeven instructie toevoegen om te verifiëren dat het eigenlijk gewoon een vraag is.

Misschien vindt u het eenvoudiger om andere methoden te verkennen, zoals externe tabellen (zoals @Kaushik suggereerde), of clientfunctionaliteit.

Zoals @kfinity in een opmerking suggereerde, zou je een ref-cursor kunnen gebruiken om de query te ontleden en uit te voeren, wat zou moeten voorkomen dat er iets vervelends wordt uitgevoerd. De dbms_sql pakket heeft een functie om een ​​ref-cursor om te zetten in een native cursor , dus met behulp van die insetad van de expliciete stappen voor openen, ontleden en uitvoeren:

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

... die hetzelfde uitvoerbestand produceert.

Overigens, als je zou willen, kun je de kolomnamen ook als koprij uitschrijven, vóór de fetch-rows-lus:

   -- 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. Wildfly 10 kan MySQL XA-stuurprogramma niet laden bij opstarten

  2. Geneste JSON maken met PHP MySQL

  3. Hoe links deelnemen aan 2 tabellen op 2 verschillende databases?

  4. PHP/MySQL rij invoegen en vervolgens 'id' ophalen