Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Przekaż SELECT STATEMENT jako parametr IN do procedury i wykonaj w Oracle

Ponieważ nie znasz kolumn, które zostaną zwrócone przez przekazane zapytanie w czasie kompilacji, nie możesz odwoływać się do nich statycznie w pętli.

Możesz użyć dbms_sql pakiet, aby zrobić to dynamicznie:

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, w zasadzie, analizuje przekazaną instrukcję, wykonuje ją, pobiera każdy wiersz, pobiera po kolei wartość każdej kolumny (jako ciąg, który można/powinien być rozszerzony, aby uniknąć niejawnych konwersji) i zapisuje je do pliku z kolei - dodanie ogranicznika między nimi i ostatniej nowej linii po każdym wierszu.

Po wywołaniu z anonimowego bloku, który tworzy plik zawierający:

NLS_CHARACTERSET;AL32UTF8
NLS_RDBMS_VERSION;11.2.0.4.0

Pamiętaj jednak, że uruchomi to wszystko, co zostanie podane, w tym DDL (który jest wykonywany podczas parsowania). Jeśli nie kontrolujesz, jak to się wywoływane, a nawet jeśli tak, powinieneś dodać walidację przekazanej instrukcji, aby sprawdzić, czy jest to w rzeczywistości tylko zapytanie.

Może się okazać, że łatwiej jest zbadać inne metody, takie jak tabele zewnętrzne (jak sugerował @Kaushik) lub funkcjonalność klienta.

Jak zasugerował @kfinity w komentarzu, możesz użyć kursora ref do przeanalizowania i wykonania zapytania, co powinno zapobiec uruchomieniu czegokolwiek nieprzyjemnego. dbms_sql pakiet posiada funkcję konwersji kursora ref na kursor natywny , więc używając tej insetad jawnego otwarcia, przeanalizuj i wykonaj kroki:

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

... co daje ten sam plik wyjściowy.

Nawiasem mówiąc, jeśli chcesz, możesz również zapisać nazwy kolumn jako wiersz nagłówka, przed pętlą 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. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wywołanie SQL Maksymalna liczba wierszy z tabeli tymczasowej

  2. Skutki zmiany ustawienia NLS_LANG w rejestrze dla Oracle Client

  3. Jak mogę zamknąć Oracle DbLinks w JDBC ze źródłami danych i transakcjami XA, aby uniknąć błędów ORA-02020?

  4. Jak usunąć duplikaty z listy rozdzielanej przecinkami według wyrażenia regularnego w Oracle, ale nie chcę duplikatów wartości?

  5. Czy można dokonać selekcji do tabeli typu obiektu?