1.ORACLE通過UTL_FILE包匯出CSV檔案
概述
在PL/SQL中,UTL_FILE包提供了文字檔案輸入和輸出互功能。也就是說我們可以通過該包實現從作業系統級別來實現檔案讀取輸入或者是寫入到作業系統檔案。通過該包也可以將其他系統的資料載入到資料庫中。如載入web伺服器日誌,使用者登入資料庫日誌乃至Oracle日誌檔案等等。本文主要描述了UTL_FILE的功能以及通過例項演示並理解這個包下相關過程函式的用法。
官網地址為https://docs.oracle.com/cd/B19306_01/appdev.102/b14258/u_file.htm#i1003488
分為以下幾個部分進行介紹
- Security Model
- Types
- Operational Notes
- Rules and Limits
- Exceptions
- Examples
1 Security Model
UTL_FILE可用於客戶端和伺服器端PL/SQL。 客戶端(文字I/O)和伺服器實現都受伺服器端檔案系統許可權檢查的約束。
在過去,使用UTL_FILE_DIR引數在初始化檔案中指定了UTL_FILE函式的可訪問目錄。 但是,不建議使用UTL_FILE_DIR訪問。 建議您使用CREATE DIRECTORY功能,該功能取代UTL_FILE_DIR。 目錄物件為UTL_FILE應用程式管理員提供了更大的靈活性和精細控制,可以動態維護(即不關閉資料庫),並與其他Oracle工具保持一致。 預設情況下,CREATE DIRECTORY許可權僅授予SYS和SYSTEM。
使用CREATE DIRECTORY功能代替UTL_FILE_DIR進行目錄訪問驗證。
2 Types
FILE_TYPE的內容對UTL_FILE包是私有的。 不應該引用或更改此記錄的元件。
TYPE file_type IS RECORD (
id BINARY_INTEGER,
datatype BINARY_INTEGER);
3 Operational Notes
UTL_FILE隱式地解釋讀取請求上的行終止符,從而影響GET_LINE呼叫返回的位元組數。 例如,UTL_FILE.GET_LINE的len引數指定所請求的字元資料的位元組數。 實際返回給使用者的位元組數將是以下值中的較小者:
- GET_LINE len引數
- 直到下一行終止符字元的位元組數
- 由UTL_FILE.FOPEN指定的max_linesize引數
FOPEN max_linesize引數必須是1和32767範圍內的數字。如果未指定,則Oracle提供預設值1024. GET_LINE len引數必須是1和32767範圍內的數字。如果未指定,則Oracle提供預設值max_linesize。 如果將max_linesize和len定義為不同的值,則較小的值優先。
當讀取在一個字符集中編碼的資料並且告知全域性化支援(例如通過NLS_LANG)它在另一個字符集中編碼時,結果是不確定的。 如果設定了NLS_LANG,則它應與資料庫字符集相同。
4 Rules and Limits
特定於作業系統的引數(例如UNIX下的C-shell環境變數)不能在檔案位置或檔名引數中使用。
UTL_FILE I/O功能類似於標準作業系統流檔案I/O(OPEN,GET,PUT,CLOSE)功能,但有一些限制。 例如,您呼叫FOPEN函式以返回檔案控制代碼,您在後續呼叫GET_LINE或PUT時使用該控制代碼來執行檔案的流I/O. 完成檔案I/O後,呼叫FCLOSE以完成與該檔案關聯的任何輸出和空閒資源。
5 Exceptions
Exception Name | Description |
---|---|
INVALID_PATH | File location is invalid. |
INVALID_MODE | The open_mode parameter in FOPEN is invalid. |
INVALID_FILEHANDLE | File handle is invalid. |
INVALID_OPERATION | File could not be opened or operated on as requested. |
READ_ERROR | Operating system error occurred during the read operation. |
WRITE_ERROR | Operating system error occurred during the write operation. |
INTERNAL_ERROR | Unspecified PL/SQL error |
CHARSETMISMATCH | A file is opened using FOPEN_NCHAR, but later I/O operations use nonchar functions such as PUTF or GET_LINE. |
FILE_OPEN | The requested operation failed because the file is open. |
INVALID_MAXLINESIZE | The MAX_LINESIZE value for FOPEN() is invalid; it should be within the range 1 to 32767. |
INVALID_FILENAME | The filename parameter is invalid. |
ACCESS_DENIED | Permission to access to the file location is denied. |
DELETE_FAILED | The requested file delete operation failed. |
RENAME_FAILED | The requested file rename operation failed. |
6 Examples
6.1 Example 1
SQL> CREATE DIRECTORY log_dir AS '/appl/gl/log';
SQL> GRANT READ ON DIRECTORY log_dir TO DBA;
SQL> CREATE DIRECTORY out_dir AS '/appl/gl/user'';
SQL> GRANT READ ON DIRECTORY user_dir TO PUBLIC;
6.2 Example 1
DECLARE
V1 VARCHAR2(32767);
F1 UTL_FILE.FILE_TYPE;
BEGIN
-- In this example MAX_LINESIZE is less than GET_LINE's length request
-- so the number of bytes returned will be 256 or less if a line terminator is seen.
F1 := UTL_FILE.FOPEN('MYDIR','MYFILE','R',256);
UTL_FILE.GET_LINE(F1,V1,32767);
UTL_FILE.FCLOSE(F1);
-- In this example, FOPEN's MAX_LINESIZE is NULL and defaults to 1024,
-- so the number of bytes returned will be 1024 or less if a line terminator is seen.
F1 := UTL_FILE.FOPEN('MYDIR','MYFILE','R');
UTL_FILE.GET_LINE(F1,V1,32767);
UTL_FILE.FCLOSE(F1);
-- In this example, GET_LINE doesn't specify a number of bytes, so it defaults to
-- the same value as FOPEN's MAX_LINESIZE which is NULL in this case and defaults to 1024.
-- So the number of bytes returned will be 1024 or less if a line terminator is seen.
F1 := UTL_FILE.FOPEN('MYDIR','MYFILE','R');
UTL_FILE.GET_LINE(F1,V1);
UTL_FILE.FCLOSE(F1);
END;
其他函式的引數等相關資訊檢視官方文件
應用
下面我介紹一下大表匯出CSV的儲存過程
1 建立directory
shell> mkdir -p /data/datadir
shell> chown -R oracle:oinstall /data/datadir
SQL> create or replace directory DATADIR as '/data/datadir';
SQL> grant read,write on directory DATADIR to public;
2 建立儲存過程
CREATE OR REPLACE PROCEDURE SQL_TO_CSV(P_QUERY IN VARCHAR2, -- PLSQL文
P_DIR IN VARCHAR2, -- 匯出的檔案放置目錄
P_FILENAME IN VARCHAR2 -- CSV名
) IS
L_OUTPUT UTL_FILE.FILE_TYPE;
L_THECURSOR INTEGER DEFAULT DBMS_SQL.OPEN_CURSOR;
L_COLUMNVALUE VARCHAR2(4000);
L_STATUS INTEGER;
L_COLCNT NUMBER := 0;
L_SEPARATOR VARCHAR2(1);
L_DESCTBL DBMS_SQL.DESC_TAB;
P_MAX_LINESIZE NUMBER := 32000;
BEGIN
--OPEN FILE
L_OUTPUT := UTL_FILE.FOPEN(P_DIR, P_FILENAME, 'W', P_MAX_LINESIZE);
--DEFINE DATE FORMAT
EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_FORMAT=''YYYYMMDDHH24MISS''';
--OPEN CURSOR
DBMS_SQL.PARSE(L_THECURSOR, P_QUERY, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(L_THECURSOR, L_COLCNT, L_DESCTBL);
--DUMP TABLE COLUMN NAME
FOR I IN 1 .. L_COLCNT LOOP
UTL_FILE.PUT(L_OUTPUT, L_SEPARATOR || L_DESCTBL(I).COL_NAME);
DBMS_SQL.DEFINE_COLUMN(L_THECURSOR, I, L_COLUMNVALUE, 4000);
L_SEPARATOR := ',';
END LOOP;
UTL_FILE.NEW_LINE(L_OUTPUT);
--EXECUTE THE QUERY STATEMENT
L_STATUS := DBMS_SQL.EXECUTE(L_THECURSOR);
--DUMP TABLE COLUMN VALUE
WHILE (DBMS_SQL.FETCH_ROWS(L_THECURSOR) > 0) LOOP
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT LOOP
DBMS_SQL.COLUMN_VALUE(L_THECURSOR, I, L_COLUMNVALUE);
UTL_FILE.PUT(L_OUTPUT,
L_SEPARATOR ||
TRIM(BOTH ' ' FROM REPLACE(L_COLUMNVALUE, '', '')) || '');
L_SEPARATOR := ',';
END LOOP;
UTL_FILE.NEW_LINE(L_OUTPUT);
END LOOP;
--CLOSE CURSOR
DBMS_SQL.CLOSE_CURSOR(L_THECURSOR);
--CLOSE FILE
UTL_FILE.FCLOSE(L_OUTPUT);
EXCEPTION
WHEN OTHERS THEN
RAISE;
END;
3 匯出資料
exec SQL_TO_CSV('select * from a where rownum <= 1000','CSVDIR','a.csv');