UTL_FILE は Oracle の組み込みパッケージで、PL/SQL プログラムからデータベースサーバー上のファイルを読み書きできます。バッチ処理での CSV 出力・ログファイルの書き出し・テキストデータの取り込みなど、実務で幅広く活用されています。
UTL_FILE が操作できるのはDBサーバー上のファイルです。クライアントPC のファイルを直接操作することはできません。ファイルのパスは DIRECTORY オブジェクトで管理します。
この記事でわかること
- DIRECTORY オブジェクトの作成と権限付与
- FOPEN / PUT_LINE / NEW_LINE / GET_LINE / FCLOSE の基本操作
- CSV ファイルを出力する実務パターン
- テキストファイルを読み込んでテーブルに INSERT するパターン
- バイナリファイル(RAW)の読み書き
- UTL_FILE 例外の種類と対処法
- UTL_FILE vs 外部表(External Table)の使い分け
事前準備:DIRECTORY オブジェクトの作成
UTL_FILE はセキュリティ上の理由から、アクセスできるパスをDIRECTORY オブジェクトとして事前に登録する必要があります。直接パス文字列を渡す旧来の方式(utl_file_dir パラメータ)はOracle 19c で完全廃止されました。必ず DIRECTORY オブジェクトを使います。
DIRECTORY の作成・確認・権限付与(DBA 権限が必要)
-- DIRECTORY オブジェクトの作成(DBA 権限が必要) -- OS 上の実際のディレクトリが存在している必要がある CREATE OR REPLACE DIRECTORY data_dir AS '/home/oracle/data'; CREATE OR REPLACE DIRECTORY log_dir AS '/home/oracle/logs'; -- 作成済み DIRECTORY の確認 SELECT directory_name, directory_path FROM dba_directories ORDER BY directory_name; -- ユーザーに READ / WRITE 権限を付与 GRANT READ, WRITE ON DIRECTORY data_dir TO app_user; GRANT READ ON DIRECTORY data_dir TO read_only_user; -- 現在ユーザーが持つ DIRECTORY 権限を確認 SELECT grantee, grantor, table_name AS directory_name, privilege FROM dba_tab_privs WHERE table_name IN (SELECT directory_name FROM dba_directories) ORDER BY grantee, directory_name;
OS 上のディレクトリが存在しない場合はエラーになる
CREATE DIRECTORY 文自体は OS ディレクトリの存在を確認しません。しかし実際にファイルを開こうとすると UTL_FILE.INVALID_PATH またはUTL_FILE.INVALID_OPERATION が発生します。Oracle プロセスオーナー(通常 oracle ユーザー)がそのディレクトリに対する読み書き権限を持っているかも確認してください。
UTL_FILE の基本操作:FOPEN / PUT_LINE / GET_LINE / FCLOSE
| プロシージャ/関数 | 説明 |
|---|---|
UTL_FILE.FOPEN(dir, file, mode, max_linesize) |
ファイルを開く。mode: ‘r’=読込 ‘w’=書込(上書) ‘a’=追記 ‘rb’=バイナリ読 ‘wb’=バイナリ書 |
UTL_FILE.PUT(fh, str) |
改行なしで文字列を書き込む |
UTL_FILE.PUT_LINE(fh, str) |
文字列を書き込み改行を追加する |
UTL_FILE.NEW_LINE(fh, lines) |
改行のみを書き込む |
UTL_FILE.GET_LINE(fh, buf, len) |
1行読み込む。EOF で NO_DATA_FOUND が発生 |
UTL_FILE.FCLOSE(fh) |
ファイルを閉じる |
UTL_FILE.FCLOSE_ALL |
セッション内の全ファイルを閉じる |
UTL_FILE.FFLUSH(fh) |
バッファをディスクに書き出す |
UTL_FILE.FREMOVE(dir, file) |
ファイルを削除する |
UTL_FILE.FRENAME(src_dir, src, dst_dir, dst, overwrite) |
ファイルを移動・リネームする |
UTL_FILE.FCOPY(src_dir, src, dst_dir, dst) |
ファイルをコピーする |
UTL_FILE.FGETATTR(dir, file, fexists, flen, block_size) |
ファイルの存在・サイズを確認する |
ファイルへの書き込み:基本パターン
テキストファイルを書き込む基本パターン
DECLARE
v_fh UTL_FILE.FILE_TYPE; -- ファイルハンドル
BEGIN
-- FOPEN(ディレクトリ名, ファイル名, モード, 最大行サイズ)
-- モード 'w' = 上書き新規作成(既存ファイルは上書き)
-- max_linesize のデフォルトは 1024、最大 32767
v_fh := UTL_FILE.FOPEN('LOG_DIR', 'process.log', 'w', 32767);
UTL_FILE.PUT_LINE(v_fh, '処理開始: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
UTL_FILE.PUT_LINE(v_fh, '対象件数: 1000');
UTL_FILE.PUT_LINE(v_fh, '処理完了');
UTL_FILE.FCLOSE(v_fh);
EXCEPTION
WHEN UTL_FILE.INVALID_PATH THEN
DBMS_OUTPUT.PUT_LINE('ディレクトリが存在しないか権限がありません');
WHEN UTL_FILE.INVALID_OPERATION THEN
DBMS_OUTPUT.PUT_LINE('ファイルのオープンに失敗しました');
WHEN OTHERS THEN
-- 例外発生時もファイルを確実にクローズする
IF UTL_FILE.IS_OPEN(v_fh) THEN
UTL_FILE.FCLOSE(v_fh);
END IF;
RAISE;
END;
/
CSV ファイルを出力する
SELECT 結果を CSV で出力するパターンは実務で非常によく使われます。カンマを含む値は二重引用符で囲む・ヘッダー行を付けるなどの考慮が必要です。
テーブルデータを CSV ファイルに出力するプロシージャ
CREATE OR REPLACE PROCEDURE export_employees_csv (
p_directory IN VARCHAR2 DEFAULT 'DATA_DIR',
p_filename IN VARCHAR2 DEFAULT 'employees.csv'
) AS
v_fh UTL_FILE.FILE_TYPE;
v_line VARCHAR2(32767);
-- CSV の値にカンマ・ダブルクォートが含まれる場合に安全に囲む関数
FUNCTION csv_val(p_val IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
IF p_val IS NULL THEN
RETURN '';
ELSIF INSTR(p_val, ',') > 0 OR INSTR(p_val, '"') > 0 OR INSTR(p_val, CHR(10)) > 0 THEN
-- カンマ・ダブルクォート・改行を含む場合はダブルクォートで囲み、
-- 内部のダブルクォートは2つに重ねてエスケープ
RETURN '"' || REPLACE(p_val, '"', '""') || '"';
ELSE
RETURN p_val;
END IF;
END csv_val;
BEGIN
v_fh := UTL_FILE.FOPEN(p_directory, p_filename, 'w', 32767);
-- ヘッダー行
UTL_FILE.PUT_LINE(v_fh, 'employee_id,last_name,first_name,salary,department_id,hire_date');
-- データ行をカーソル FOR LOOP で出力
FOR r IN (
SELECT employee_id, last_name, first_name,
salary, department_id,
TO_CHAR(hire_date, 'YYYY-MM-DD') AS hire_date
FROM employees
ORDER BY employee_id
) LOOP
v_line := r.employee_id || ','
|| csv_val(r.last_name) || ','
|| csv_val(r.first_name) || ','
|| NVL(TO_CHAR(r.salary), '') || ','
|| NVL(TO_CHAR(r.department_id),'') || ','
|| r.hire_date;
UTL_FILE.PUT_LINE(v_fh, v_line);
END LOOP;
UTL_FILE.FCLOSE(v_fh);
DBMS_OUTPUT.PUT_LINE(p_filename || ' の出力が完了しました');
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(v_fh) THEN
UTL_FILE.FCLOSE(v_fh);
END IF;
RAISE;
END export_employees_csv;
/
-- 実行例
BEGIN
export_employees_csv('DATA_DIR', 'emp_20250406.csv');
END;
/
テキストファイルを読み込む
GET_LINE は1行読み込むごとに行末の改行を除いた文字列を返します。ファイルの終端に達すると NO_DATA_FOUND 例外が発生するので、それをループ終了のシグナルとして使います。
テキストファイルを読み込んでログに出力する
DECLARE
v_fh UTL_FILE.FILE_TYPE;
v_line VARCHAR2(32767);
v_lineno NUMBER := 0;
BEGIN
-- モード 'r' = 読み込み専用
v_fh := UTL_FILE.FOPEN('DATA_DIR', 'input.txt', 'r', 32767);
LOOP
BEGIN
UTL_FILE.GET_LINE(v_fh, v_line); -- 1行読み込む
v_lineno := v_lineno + 1;
DBMS_OUTPUT.PUT_LINE(v_lineno || ': ' || v_line);
EXCEPTION
WHEN NO_DATA_FOUND THEN
EXIT; -- EOF に達したらループを抜ける
END;
END LOOP;
UTL_FILE.FCLOSE(v_fh);
DBMS_OUTPUT.PUT_LINE('読み込み完了: ' || v_lineno || '行');
EXCEPTION
WHEN UTL_FILE.INVALID_PATH THEN
DBMS_OUTPUT.PUT_LINE('ディレクトリまたはファイルが見つかりません');
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(v_fh) THEN
UTL_FILE.FCLOSE(v_fh);
END IF;
RAISE;
END;
/
CSV ファイルを読み込んでテーブルに INSERT する
CREATE OR REPLACE PROCEDURE import_csv (
p_directory IN VARCHAR2,
p_filename IN VARCHAR2
) AS
v_fh UTL_FILE.FILE_TYPE;
v_line VARCHAR2(32767);
v_lineno PLS_INTEGER := 0;
v_loaded PLS_INTEGER := 0;
-- カンマ区切りの n 番目フィールドを取得するヘルパー
FUNCTION get_field(p_line VARCHAR2, p_pos PLS_INTEGER) RETURN VARCHAR2 IS
v_start PLS_INTEGER;
v_end PLS_INTEGER;
BEGIN
v_start := CASE p_pos WHEN 1 THEN 1
ELSE INSTR(p_line, ',', 1, p_pos - 1) + 1 END;
v_end := INSTR(p_line, ',', 1, p_pos);
IF v_end = 0 THEN
RETURN SUBSTR(p_line, v_start); -- 最後のフィールド
ELSE
RETURN SUBSTR(p_line, v_start, v_end - v_start);
END IF;
END get_field;
BEGIN
v_fh := UTL_FILE.FOPEN(p_directory, p_filename, 'r', 32767);
LOOP
BEGIN
UTL_FILE.GET_LINE(v_fh, v_line);
v_lineno := v_lineno + 1;
-- 1行目(ヘッダー)はスキップ
IF v_lineno = 1 THEN CONTINUE; END IF;
-- 空行はスキップ
IF TRIM(v_line) IS NULL THEN CONTINUE; END IF;
INSERT INTO emp_staging (emp_id, last_name, salary, hire_date)
VALUES (
TO_NUMBER(get_field(v_line, 1)),
get_field(v_line, 2),
TO_NUMBER(get_field(v_line, 3)),
TO_DATE(get_field(v_line, 4), 'YYYY-MM-DD')
);
v_loaded := v_loaded + 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN EXIT; -- EOF
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('行' || v_lineno || 'エラー: ' || SQLERRM);
-- エラー行をスキップして続行する場合はここで CONTINUE
END;
END LOOP;
COMMIT;
UTL_FILE.FCLOSE(v_fh);
DBMS_OUTPUT.PUT_LINE(v_loaded || '件 INSERT しました');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
IF UTL_FILE.IS_OPEN(v_fh) THEN UTL_FILE.FCLOSE(v_fh); END IF;
RAISE;
END import_csv;
/
ファイルの存在確認・削除・リネーム
FGETATTR でファイル存在確認・FREMOVE で削除
DECLARE
v_exists BOOLEAN;
v_file_len NUMBER;
v_block_size BINARY_INTEGER;
BEGIN
-- ファイルの存在確認とサイズ取得(FGETATTR)
UTL_FILE.FGETATTR(
location => 'DATA_DIR',
filename => 'output.csv',
fexists => v_exists,
file_length => v_file_len,
block_size => v_block_size
);
IF v_exists THEN
DBMS_OUTPUT.PUT_LINE('ファイルサイズ: ' || v_file_len || ' バイト');
-- 古いファイルをバックアップ用にリネームしてから新規作成
UTL_FILE.FRENAME(
src_location => 'DATA_DIR',
src_filename => 'output.csv',
dest_location => 'DATA_DIR',
dest_filename => 'output_' || TO_CHAR(SYSDATE, 'YYYYMMDD') || '.bak',
overwrite => FALSE -- TRUE にすると同名ファイルを上書き
);
DBMS_OUTPUT.PUT_LINE('バックアップ完了');
ELSE
DBMS_OUTPUT.PUT_LINE('ファイルが存在しません');
END IF;
-- ファイルの削除(存在しない場合は UTL_FILE.INVALID_OPERATION が発生)
-- UTL_FILE.FREMOVE('DATA_DIR', 'old_file.txt');
END;
/
UTL_FILE の例外一覧と対処法
| 例外 | 発生する状況 | 対処 |
|---|---|---|
INVALID_PATH |
DIRECTORY が存在しない・権限がない・OSパスが存在しない | DIRECTORY と OS ディレクトリの両方を確認。権限付与を確認 |
INVALID_MODE |
FOPEN のモード文字列が不正 | ‘r”w”a”rb”wb”ab’のいずれかを指定 |
INVALID_FILEHANDLE |
クローズ済みのハンドルを使用 | IS_OPEN で確認してから操作する |
INVALID_OPERATION |
読み込みモードのファイルへの書き込み等、操作が不正 | モードと操作が一致しているか確認 |
READ_ERROR |
ファイル読み込み中にOSエラー | ファイルの破損・権限を確認 |
WRITE_ERROR |
ファイル書き込み中にOSエラー(ディスク満杯等) | ディスク空き容量・書き込み権限を確認 |
INTERNAL_ERROR |
UTL_FILE 内部エラー | Oracle サポートへ連絡 |
NO_DATA_FOUND |
GET_LINE で EOF に達した | ループ終了のシグナルとして使用(正常) |
UTL_FILE vs 外部表(External Table)の使い分け
| 観点 | UTL_FILE | 外部表(External Table) |
|---|---|---|
| 操作方向 | 読み込み・書き込みの両方 | 読み込みのみ(SELECT専用) |
| 出力(書き込み) | 可能(CSV出力・ログ出力) | 不可 |
| 書き方 | PL/SQL コードが必要 | SQL の SELECT で使える |
| 大量データ読み込み | 行ごとのループ処理(遅い) | SQL ベースで高速(パラレル処理も可能) |
| フォーマット柔軟性 | 行単位で自由にパース可能 | 固定フォーマットの CSV・固定長に適す |
| エラー行のスキップ | PL/SQL の EXCEPTION で制御可能 | REJECT LIMIT で制御 |
使い分けの基準
- CSV・ログを書き出したい → UTL_FILE(外部表は書き込み不可)
- 大量データを高速に読み込みたい → 外部表(SQL ベースで内部結合も可能)
- 行ごとに複雑なパースや変換が必要 → UTL_FILE(PL/SQL で自由に処理)
まとめ
- UTL_FILE はDBサーバー上のファイルを PL/SQL から操作するパッケージ
- アクセスできるパスは DIRECTORY オブジェクトで管理(
utl_file_dirは19cで廃止) - FOPEN → PUT_LINE/GET_LINE → FCLOSE が基本の流れ。例外発生時も FCLOSE を確実に呼ぶ
- CSV 出力ではカンマ・ダブルクォート・改行を含む値を RFC 4180 形式でエスケープする
- 読み込みは GET_LINE で NO_DATA_FOUND → EXIT がループ終了の定番パターン
- ファイルの存在確認は
FGETATTR、削除はFREMOVE、移動はFRENAME - 大量データの読み込みには外部表の方が高速。UTL_FILE は書き出しと複雑なパースに強い
UTL_FILE と組み合わせて使う例外処理の詳細はPL/SQL 例外処理完全ガイドを、CSVファイルをSQLで直接読み込む外部表については外部表(External Table)の使い方完全ガイドも参照してください。

