Oracle
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Oracle

Chuyển SELECT STATEMENT làm tham số IN để thủ tục và thực thi trong Oracle

Vì bạn không biết các cột sẽ được trả về bởi truy vấn chuyển vào lúc biên dịch, bạn không thể tham chiếu đến chúng bên trong vòng lặp một cách tĩnh.

Bạn có thể sử dụng dbms_sql gói để thực hiện việc này một cách động:

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

Về cơ bản, phân tích cú pháp câu lệnh được truyền vào, thực thi nó, tìm nạp từng hàng, lần lượt nhận từng giá trị cột (dưới dạng một chuỗi, có thể / nên được mở rộng để tránh chuyển đổi ngầm định) và ghi một trong số đó vào tệp lần lượt - thêm dấu phân cách giữa chúng và một dòng mới cuối cùng sau mỗi hàng.

Khi được gọi từ khối ẩn danh của bạn, nó sẽ tạo ra một tệp chứa:

NLS_CHARACTERSET;AL32UTF8
NLS_RDBMS_VERSION;11.2.0.4.0

Hãy lưu ý, thoguh, điều này sẽ chạy bất kỳ thứ gì mà nó được đưa ra, bao gồm cả DDL (được thực thi khi được phân tích cú pháp). Nếu bạn không kiểm soát cách gọi này và thực sự thì ngay cả khi bạn làm vậy, bạn nên thêm xác thực câu lệnh được chuyển vào để xác minh rằng đó thực sự chỉ là một truy vấn.

Bạn có thể thấy đơn giản hơn khi khám phá các phương pháp khác, chẳng hạn như bảng bên ngoài (như @Kaushik đề xuất) hoặc chức năng ứng dụng khách.

Như @kfinity đã đề xuất trong một nhận xét, bạn có thể sử dụng con trỏ tham chiếu để phân tích cú pháp và thực hiện truy vấn, điều này sẽ ngăn không cho chạy bất kỳ điều gì khó chịu nào. dbms_sql package có chức năng chuyển đổi con trỏ tham chiếu thành con trỏ gốc , do đó, sử dụng phần chèn của các bước mở, phân tích cú pháp và thực thi rõ ràng:

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

... tạo ra cùng một tệp đầu ra.

Ngẫu nhiên, nếu bạn muốn, bạn cũng có thể viết tên cột dưới dạng hàng tiêu đề, trước vòng lặp tìm nạp hàng:

   -- 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. Có bất kỳ lý do hợp lý nào của việc có các vùng bảng khác nhau cho các chỉ mục không?

  2. Các ngày khác nhau Oracle 11g với TOAD

  3. Lỗi SQL:ORA-02291:ràng buộc toàn vẹn

  4. Tìm nạp và thu thập hàng loạt từ REF CURSOR được trả về bởi một thủ tục

  5. Làm thế nào để nhiều hàng có thể được nối thành một trong Oracle mà không cần tạo một thủ tục được lưu trữ?