UTL_FILE は、Oracle Databaseサーバ上のファイルをPL/SQLから読み書きするための標準パッケージです。CSV出力、バッチログ、連携ファイルの読み込み、障害調査用の一時ファイル出力など、業務バッチでよく使われます。
この記事では、UTL_FILE の基本仕様をなぞるだけでなく、PL/SQL側でよく使う「CSVを書き出す」「CSVを1行ずつ読む」「ログを追記する」「例外時に必ず閉じる」「ファイル名を検証する」という実務レシピを中心に整理します。網羅的な仕様確認は Oracle UTL_FILE完全ガイド、大量データのジョブ化は DBMS_SCHEDULER完全ガイド もあわせて確認してください。
CREATE DIRECTORYと権限設定FOPEN、PUT_LINE、GET_LINE、FCLOSEの基本- CSV出力、CSV読み込み、ログ追記の実装例
FFLUSH、FCLOSE_ALL、FOPEN_NCHARの使いどころORA-29283、INVALID_PATH、NO_DATA_FOUNDの切り分け- 外部表、
DBMS_LOB、DBMS_OUTPUTとの使い分け
UTL_FILEを使う前提
UTL_FILE が扱うファイルは、クライアントPCではなくデータベースサーバ上のファイルです。SQL Developerやアプリケーションサーバのローカルファイルを直接読むわけではありません。そのため、OS上のディレクトリ権限、Oracleのディレクトリオブジェクト、データベースユーザーへの権限付与をセットで確認します。
DIRECTORY 権限の両方が必要です。max_linesize は最大 32767 です。長大行やLOBは別設計を検討します。DIRECTORYを作成して権限を付与する
現在のOracleでは、古い UTL_FILE_DIR 初期化パラメータではなく、ディレクトリオブジェクトを使うのが基本です。ディレクトリオブジェクトはDBAまたは必要権限を持つユーザーが作成し、実行ユーザーへ READ / WRITE を付与します。
-- DBAまたは権限を持つユーザーで実行 CREATE OR REPLACE DIRECTORY APP_EXPORT_DIR AS '/u01/app/export'; GRANT READ, WRITE ON DIRECTORY APP_EXPORT_DIR TO APP_USER; -- 必要に応じて確認 SELECT directory_name, directory_path FROM all_directories WHERE directory_name = 'APP_EXPORT_DIR';
Oracle側の権限だけでなく、OSユーザーがそのディレクトリへ読み書きできる必要があります。ORA-29283: invalid file operation が出る場合は、ディレクトリ名の誤り、OSパスの存在、OS権限、ファイル名、モード、改行や文字コードを順に確認します。
CSVを書き出す基本形
CSV出力では、FOPEN でファイルを開き、PUT_LINE で1行ずつ書き、最後に FCLOSE で閉じます。例外が発生してもハンドルを閉じるように、IS_OPEN を使って後始末します。
DECLARE
l_file UTL_FILE.FILE_TYPE;
BEGIN
l_file := UTL_FILE.FOPEN(
location => 'APP_EXPORT_DIR',
filename => 'orders.csv',
open_mode => 'w',
max_linesize => 32767
);
UTL_FILE.PUT_LINE(l_file, 'ORDER_ID,CUSTOMER_ID,AMOUNT');
FOR r IN (
SELECT order_id, customer_id, amount
FROM orders
ORDER BY order_id
) LOOP
UTL_FILE.PUT_LINE(
l_file,
r.order_id || ',' ||
r.customer_id || ',' ||
r.amount
);
END LOOP;
UTL_FILE.FCLOSE(l_file);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(l_file) THEN
UTL_FILE.FCLOSE(l_file);
END IF;
RAISE;
END;
/
CSVの値にカンマ、ダブルクォート、改行が含まれる可能性がある場合は、単純連結では壊れます。実務ではCSVエスケープ関数を用意し、文字列列は必ずダブルクォートで囲む設計にすると安全です。
CSVエスケープ関数を用意する
CSVは見た目よりも事故りやすい形式です。顧客名にカンマが入る、備考に改行が入る、ダブルクォートが入る、といったケースを想定しておきます。
CREATE OR REPLACE FUNCTION csv_value(p_value IN VARCHAR2)
RETURN VARCHAR2
AS
BEGIN
IF p_value IS NULL THEN
RETURN '""';
END IF;
RETURN '"' || REPLACE(p_value, '"', '""') || '"';
END;
/
-- 使用例
SELECT csv_value('A,B "test"') AS escaped_value
FROM dual;
数値や日付も、環境依存の書式で出力すると取り込み側で失敗します。日付は TO_CHAR(order_date, 'YYYY-MM-DD HH24:MI:SS') のように明示し、数値も小数点や桁区切りのNLS差に注意します。
CSVを1行ずつ読み込む
ファイル読み込みでは、GET_LINE をループで呼び出します。ファイル終端に到達すると NO_DATA_FOUND が発生するため、これは異常ではなく正常終了として扱います。
DECLARE
l_file UTL_FILE.FILE_TYPE;
l_line VARCHAR2(32767);
BEGIN
l_file := UTL_FILE.FOPEN(
location => 'APP_EXPORT_DIR',
filename => 'orders.csv',
open_mode => 'r',
max_linesize => 32767
);
LOOP
BEGIN
UTL_FILE.GET_LINE(l_file, l_line);
-- ここでCSVを解析してテーブルへ取り込む
DBMS_OUTPUT.PUT_LINE(l_line);
EXCEPTION
WHEN NO_DATA_FOUND THEN
EXIT;
END;
END LOOP;
UTL_FILE.FCLOSE(l_file);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(l_file) THEN
UTL_FILE.FCLOSE(l_file);
END IF;
RAISE;
END;
/
NO_DATA_FOUND は通常の SELECT INTO でも使われる例外です。意味を取り違えやすい場合は ORA-01403 完全ガイド も確認してください。デバッグ出力は DBMS_OUTPUTの使い方 が参考になります。
ログファイルへ追記する
バッチの軽い実行ログをファイルへ追記したい場合は、open_mode => 'a' を使います。ただし、監査性や検索性が必要なログはファイルではなくログテーブルへ残す方が扱いやすいです。
CREATE OR REPLACE PROCEDURE write_batch_log(
p_message IN VARCHAR2
) AS
l_file UTL_FILE.FILE_TYPE;
BEGIN
l_file := UTL_FILE.FOPEN(
location => 'APP_EXPORT_DIR',
filename => 'batch.log',
open_mode => 'a',
max_linesize => 32767
);
UTL_FILE.PUT_LINE(
l_file,
TO_CHAR(SYSTIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.FF3') ||
' ' || p_message
);
UTL_FILE.FFLUSH(l_file);
UTL_FILE.FCLOSE(l_file);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(l_file) THEN
UTL_FILE.FCLOSE(l_file);
END IF;
RAISE;
END;
/
FFLUSH はバッファ内容をファイルへ反映したい場面で使います。長時間処理の途中経過を外部から見たい場合には役立ちますが、頻繁に呼びすぎるとI/O負荷が増えるため、行数や処理単位で調整します。
ファイル名を検証する
UTL_FILE ではディレクトリオブジェクトで場所を限定できますが、ファイル名を外部入力から受け取る場合は検証が必要です。パス区切り文字、相対パス、制御文字、想定外の拡張子を拒否し、できればサーバ側で決めたファイル名だけを使います。
CREATE OR REPLACE FUNCTION safe_export_filename(
p_filename IN VARCHAR2
) RETURN VARCHAR2
AS
BEGIN
IF p_filename IS NULL THEN
RAISE_APPLICATION_ERROR(-20001, 'filename is required');
END IF;
IF NOT REGEXP_LIKE(p_filename, '^[A-Za-z0-9_-]+\.csv$') THEN
RAISE_APPLICATION_ERROR(-20002, 'invalid filename');
END IF;
RETURN p_filename;
END;
/
ファイル名に ../ やサブディレクトリを渡して任意ファイルを触れるような設計は避けます。ファイル連携では「置き場所はDIRECTORYで固定」「名前は許可パターンで固定」「拡張子も固定」を基本にします。
NCHAR系ファイルを扱う場合
文字コードや国際化文字セットを意識する場合、FOPEN_NCHAR、GET_LINE_NCHAR、PUT_LINE_NCHAR も候補になります。ただし、相手システムが期待する文字コード、BOMの有無、改行コードを確認しないと、ファイルは作れても取り込み側で文字化けします。
DECLARE
l_file UTL_FILE.FILE_TYPE;
BEGIN
l_file := UTL_FILE.FOPEN_NCHAR(
location => 'APP_EXPORT_DIR',
filename => 'message.txt',
open_mode => 'w',
max_linesize => 32767
);
UTL_FILE.PUT_LINE_NCHAR(l_file, N'日本語メッセージ');
UTL_FILE.FCLOSE(l_file);
END;
/
例外処理とFCLOSE_ALL
ファイル処理では、例外時にファイルハンドルを閉じることが重要です。通常は個別に FCLOSE しますが、複数ファイルを扱う複雑な処理では、最後の保険として FCLOSE_ALL を使うこともあります。
DECLARE
l_in_file UTL_FILE.FILE_TYPE;
l_out_file UTL_FILE.FILE_TYPE;
BEGIN
l_in_file := UTL_FILE.FOPEN('APP_EXPORT_DIR', 'input.csv', 'r', 32767);
l_out_file := UTL_FILE.FOPEN('APP_EXPORT_DIR', 'output.csv', 'w', 32767);
-- file processing
NULL;
UTL_FILE.FCLOSE(l_in_file);
UTL_FILE.FCLOSE(l_out_file);
EXCEPTION
WHEN OTHERS THEN
UTL_FILE.FCLOSE_ALL;
RAISE;
END;
/
FCLOSE_ALL は便利ですが、乱用するとどのファイルをどこで閉じたのか追いづらくなります。基本はハンドルごとに閉じ、最後の安全網として使うくらいが扱いやすいです。
よくあるエラーと確認ポイント
DIRECTORY 名が違う、権限がない、OSパスが存在しない可能性があります。r、w、a などのモード指定を確認します。GET_LINE のEOFなら正常終了として扱います。max_linesize を超えていないか、受け取り変数が短くないか確認します。大量CSVは外部表も検討する
UTL_FILE で1行ずつCSVを読む方法は柔軟ですが、大量データの取り込みには向かないことがあります。大量CSVをSQLで検索・検証・一括ロードしたい場合は、外部表やSQL*Loaderなどの利用も検討します。
大きなファイルやLOBを扱う場合は DBMS_LOB完全ガイド、CLOB/BLOB変換時の ORA-22835 は ORA-22835の原因と解決方法 が参考になります。
ジョブ連携時の運用ポイント
CSV出力やファイル取り込みは、手動実行ではなくジョブとして動かすことが多いです。ジョブ化する場合は、ファイル名の日時付与、二重起動防止、処理済みファイルの退避、失敗時の再実行、ログ保存期間を決めておきます。
CREATE OR REPLACE PROCEDURE export_orders_daily AS
l_filename VARCHAR2(128);
BEGIN
l_filename := 'orders_' || TO_CHAR(SYSDATE, 'YYYYMMDD') || '.csv';
l_filename := safe_export_filename(l_filename);
-- 実際のCSV出力処理を呼び出す
-- write_orders_csv(l_filename);
INSERT INTO batch_log(process_name, status, message, created_at)
VALUES ('export_orders_daily', 'SUCCESS', l_filename, SYSDATE);
EXCEPTION
WHEN OTHERS THEN
INSERT INTO batch_log(process_name, status, message, created_at)
VALUES ('export_orders_daily', 'ERROR', SQLERRM, SYSDATE);
RAISE;
END;
/
ジョブ履歴を集めて監視する設計は ジョブ実行履歴の集中管理と異常検知 が参考になります。簡易的な動作確認では DBMS_OUTPUT も使えますが、本番運用ログはテーブルへ残す方が検索・集計しやすいです。
本番導入前チェックリスト
READ / WRITE が付与されているか。FCLOSE できるか。EOFの NO_DATA_FOUND を正常終了として扱えるか。まとめ
UTL_FILE は、PL/SQLからCSVやログファイルを読み書きする便利なパッケージです。実務では、ディレクトリオブジェクトと権限設定、FOPEN / GET_LINE / PUT_LINE / FCLOSE の基本、例外時の後始末、ファイル名検証をセットで考える必要があります。
小さなファイル連携やログ出力なら UTL_FILE が扱いやすい一方、大量CSVは外部表、LOBは DBMS_LOB、定期実行は DBMS_SCHEDULER と組み合わせると安定します。ファイル処理はOS・DB・業務運用の境界にあるため、本番前のチェックを丁寧に行いましょう。

