SQL*Loader(sqlldr)は、CSV や固定長ファイルなどのフラットファイルからOracle テーブルへデータを一括ロードするコマンドラインツールです。外部表(External Table)が SQL で参照するのに対し、SQL*Loader はファイルの内容を実際にテーブルへ書き込むため、大量データの初期投入・定期バッチでのデータ取り込みに広く使われます。
この記事でわかること
- SQL*Loader の基本コマンドと主要パラメータ
- コントロールファイル(.ctl)の書き方と必須キーワード
- APPEND・REPLACE・TRUNCATE・INSERT の4つのロードモードの違い
- 区切り文字・囲み文字・日付フォーマットの指定方法
- ダイレクトパスロード(DIRECT=TRUE)で高速化する方法
- BADファイル・DISCARDファイルでエラー行を確認する方法
- よくあるエラーと対処法
SQL*Loader とは・外部表との違い
| 比較項目 | SQL*Loader(sqlldr) | 外部表(External Table) |
|---|---|---|
| 動作 | ファイルのデータをテーブルに書き込む | ファイルをテーブルのように SELECT する(書き込まない) |
| 使用シーン | データの初期投入・定期取り込みバッチ | 一時的な参照・ETL の中間処理 |
| 設定ファイル | コントロールファイル(.ctl) | CREATE TABLE … ORGANIZATION EXTERNAL |
| エラー行 | BAD ファイルに書き出し・処理を継続できる | アクセス時にエラー(REJECT LIMIT で制御可) |
| 高速化オプション | DIRECT=TRUE(ダイレクトパスロード) | PARALLEL オプション |
基本コマンドと主要パラメータ
sqlldr の基本コマンド
# 基本構文 sqlldr userid=ユーザー名/パスワード@接続文字列 control=コントロールファイル # 例: ローカル接続 sqlldr userid=scott/tiger control=load_emp.ctl # 例: TNS 接続文字列指定 sqlldr userid=scott/tiger@ORCL control=load_emp.ctl # 主要オプション一覧 sqlldr userid=scott/tiger@ORCL \ control=load_emp.ctl \ # コントロールファイル(必須) log=load_emp.log \ # ログファイル(デフォルト: control名.log) bad=load_emp.bad \ # BADファイル(ロード失敗行の出力先) discard=load_emp.dsc \ # DISCARDファイル(WHEN句で除外された行の出力先) errors=100 \ # 許容エラー件数(超えると中断、デフォルト: 50) skip=1 \ # 先頭から指定行をスキップ(ヘッダー行の読み飛ばし) rows=10000 \ # ダイレクトパス時のコミット間隔(行数) direct=true # ダイレクトパスロードで高速化
コントロールファイル(.ctl)の書き方
コントロールファイルは SQL*Loader の中核で、どのファイルをどのテーブルにどう読み込むかを定義します。
基本的なコントロールファイル(CSV対応)
-- load_emp.ctl
LOAD DATA
INFILE 'employees.csv' -- 読み込むデータファイルのパス
APPEND -- ロードモード(後述)
INTO TABLE employees -- ロード先テーブル
FIELDS TERMINATED BY ',' -- 区切り文字: カンマ
OPTIONALLY ENCLOSED BY '"' -- 囲み文字: ダブルクォート(省略可能)
TRAILING NULLCOLS -- 末尾の列がない場合は NULL として扱う
(
employee_id, -- 列名(テーブルの列と対応)
first_name,
last_name,
email,
hire_date DATE "YYYY/MM/DD", -- 日付フォーマットを指定
salary,
department_id
)
読み込むデータファイルの例(employees.csv)
1001,田中,太郎,tanaka@example.com,2020/04/01,350000,10 1002,佐藤,花子,sato@example.com,2021/07/15,300000,20 1003,鈴木,一郎,suzuki@example.com,2022/01/10,280000,10
実行とログ確認
# ロード実行 sqlldr userid=scott/tiger control=load_emp.ctl log=load_emp.log skip=1 # ログファイルで結果確認 cat load_emp.log # → "3 Rows successfully loaded." など結果が記載される # → エラーがあれば "Row 2: Rejected" などの詳細が記録される
4つのロードモードの違い
| モード | 既存データ | 動作 | 主な用途 |
|---|---|---|---|
| INSERT(デフォルト) | そのまま | テーブルが空でないとエラー(SQL*Loader-601) | 初回ロード専用 |
| APPEND | そのまま | 既存データに追加 | 差分追加・定期ロード |
| REPLACE | DELETE で全削除後に追加 | トリガーが発火・UNDO 生成あり | 全件入れ替え(小テーブル) |
| TRUNCATE | TRUNCATE で全削除後に追加 | TRUNCATE のため高速・ロールバック不可 | 全件入れ替え(大テーブル) |
REPLACE と TRUNCATE の違いに注意
REPLACE は DELETE を実行するため UNDO が生成され時間がかかります。また行削除トリガーが発火します。TRUNCATE は DDL のため高速ですがロールバックできません。大量データを全件入れ替える場合は TRUNCATE、トリガーやロールバックが必要な場合は REPLACE を使います。
REPLACE は DELETE を実行するため UNDO が生成され時間がかかります。また行削除トリガーが発火します。TRUNCATE は DDL のため高速ですがロールバックできません。大量データを全件入れ替える場合は TRUNCATE、トリガーやロールバックが必要な場合は REPLACE を使います。
区切り文字・囲み文字・固定長の指定
タブ区切り・パイプ区切り・固定長のコントロールファイル例
-- タブ区切りの場合
FIELDS TERMINATED BY X'09' -- タブ文字(16進数 09)
FIELDS TERMINATED BY '\t' -- または \t(バージョンにより対応)
-- パイプ区切りの場合
FIELDS TERMINATED BY '|'
-- 固定長ファイルの場合(POSITION で列の開始・終了バイトを指定)
LOAD DATA
INFILE 'employees_fixed.dat'
APPEND
INTO TABLE employees
(
employee_id POSITION(1:6) INTEGER EXTERNAL,
first_name POSITION(7:26) CHAR,
last_name POSITION(27:46) CHAR,
hire_date POSITION(47:56) DATE "YYYY/MM/DD",
salary POSITION(57:65) DECIMAL EXTERNAL
)
-- 複数ファイルを一度にロードする
LOAD DATA
INFILE 'data_jan.csv'
INFILE 'data_feb.csv'
INFILE 'data_mar.csv'
APPEND
INTO TABLE monthly_sales
FIELDS TERMINATED BY ','
( year, month, amount )
NULL・条件フィルター・値変換
NULLIF・WHEN 句・定数値の設定
LOAD DATA
INFILE 'orders.csv'
APPEND
INTO TABLE orders
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
order_id,
customer_id,
-- NULLIF: 値が空文字列または特定値の場合に NULL として扱う
amount NULLIF amount=BLANKS, -- 空白なら NULL
discount NULLIF discount='0', -- '0' なら NULL
-- 定数値を埋め込む(ファイルに列がない場合)
created_date SYSDATE, -- 常に現在日時(SYSDATE は SQL*Loader のキーワード)
data_source CONSTANT 'CSV_IMPORT', -- 固定文字列(シングルクォート・CONSTANT キーワード)
-- ファイルの列を読み飛ばす(FILLER: ダミー列)
dummy_col FILLER, -- この列のデータは捨てる
-- 文字変換: SQL 関数は使えないが DECODE 式は使える
status_code "DECODE(:status_code,'1','ACTIVE','0','INACTIVE','UNKNOWN')"
)
-- WHEN 句: 条件に一致する行だけロードする(複数テーブルへの振り分けも可能)
LOAD DATA
INFILE 'employees_all.csv'
APPEND
INTO TABLE employees_sales
WHEN department_id = '10' -- 部門コード 10 のみロード
FIELDS TERMINATED BY ','
( employee_id, first_name, last_name, department_id, salary )
INTO TABLE employees_hr
WHEN department_id = '20' -- 部門コード 20 のみ別テーブルへ
FIELDS TERMINATED BY ','
( employee_id, first_name, last_name, department_id, salary )
ダイレクトパスロードで高速化する
通常のロード(コンベンショナルパス)は INSERT 文と同等の処理を行います。ダイレクトパスロード(DIRECT=TRUE)は、SQL エンジンを介さずにデータブロックを直接書き込むため、大量データの場合に大幅に高速化します。
| 比較項目 | コンベンショナルパス | ダイレクトパスロード |
|---|---|---|
| UNDO 生成 | あり(ロールバック可能) | なし(ロールバック不可) |
| redo ログ | 生成される | NOLOGGING 指定で大幅削減 |
| トリガー | 発火する | 発火しない |
| 制約チェック | INSERT 時に即時チェック | ロード後に一括チェック(PRIMARY KEY等) |
| 他セッションのアクセス | 可能 | ロード中はテーブルがロックされる |
ダイレクトパスロードの実行例
# DIRECT=TRUE で高速ロード # NOLOGGING と組み合わせると更に高速化(REDO 最小化) sqlldr userid=scott/tiger@ORCL \ control=load_orders.ctl \ direct=true \ rows=50000 \ log=load_orders.log # NOLOGGING の設定(事前にテーブルを NOLOGGING に変更) # ALTER TABLE orders NOLOGGING; # ダイレクトパス後に LOGGING に戻す場合: # ALTER TABLE orders LOGGING; # ※ NOLOGGING 設定中にメディアリカバリが必要になると、そのデータは復旧できない # → 本番では RMAN バックアップの直後に行うか、データ再ロードで対応できる場合のみ使用
BADファイル・DISCARDファイルでエラー行を確認する
ロードに失敗した行と除外された行は、それぞれ別のファイルに書き出されます。
- BADファイル(.bad):データ型不一致・制約違反などでロード失敗した行。元のデータ形式のまま保存されるため、修正後に再ロードできる
- DISCARDファイル(.dsc):コントロールファイルの WHEN 句の条件に一致せず、どのテーブルにもロードされなかった行
BADファイルを使った再ロードの流れ
# 初回ロード: 一部の行が型変換エラーでBADファイルに書き出される sqlldr userid=scott/tiger control=load_emp.ctl log=load_emp.log bad=load_emp.bad # ログファイルで結果確認 grep -E "Rows (successfully|not loaded)" load_emp.log # → "100 Rows successfully loaded." # → "3 Rows not loaded due to data errors." # BADファイルの中身を確認(失敗した行のデータがそのまま残っている) cat load_emp.bad # BADファイルを修正して再ロード(APPEND モードで追加) # vi load_emp.bad # 日付フォーマット等を修正 sqlldr userid=scott/tiger control=load_emp.ctl log=reload.log bad=reload.bad
よくあるエラーと対処法
| エラー | 原因 | 対処法 |
|---|---|---|
| ORA-00917: カンマがありません | コントロールファイルの構文エラー | .ctl ファイルの括弧・カンマ・引用符を確認する |
| ORA-01843: 月が無効です | 日付フォーマットが一致しない | DATE “YYYY-MM-DD” の形式をデータに合わせる |
| ORA-01722: 数値が無効です | 数値列に文字データが入っている | BADファイルでエラー行を確認し、データを修正する |
| SQL*Loader-601: For INSERT option, table must be empty | INSERT モードでテーブルが空でない | ロードモードを APPEND または TRUNCATE に変更する |
| SQL*Loader-350: Syntax error | コントロールファイルの構文エラー | エラー行番号を確認して .ctl ファイルを修正する |
| SQL*Loader-500: Unable to open file | データファイルが見つからない | INFILE に指定したパスとファイル名を確認する |
| SQL*Loader-926: OCI error | 接続エラーまたは権限不足 | userid・パスワード・接続文字列と INSERT 権限を確認する |
コントロールファイルの構文だけ検証する(–dryrun)
# データをロードせずにコントロールファイルの検証のみ行う # LOAD=0 で行数を 0 に指定すると実際のロードをスキップできる sqlldr userid=scott/tiger control=load_emp.ctl rows=0 log=validate.log # ログで "Elapsed time was:" と "0 Rows loaded" が出れば構文は正常 cat validate.log
まとめ
- コントロールファイル(.ctl)が SQL*Loader の核心。INFILE・INTO TABLE・FIELDS TERMINATED BY・列定義の4つを正しく書く
- ロードモード:通常の追加は APPEND、全件入れ替えは TRUNCATE を使う(INSERT はテーブルが空の場合のみ)
- 日付は必ずフォーマット指定:
hire_date DATE "YYYY/MM/DD"のように列ごとに書式を指定する - ダイレクトパス(DIRECT=TRUE):大量ロードを高速化するが、トリガー非発火・ロード中テーブルロックに注意する
- BADファイルで再ロードに備える:エラー行は BAD ファイルに書き出されるため、修正後に再ロード可能
SQL を使わずファイルをテーブルのように参照したい場合は外部表(External Table)完全ガイドも参照してください。大量データの移行やスキーマ単位の入出力にはData Pump(expdp/impdp)完全ガイドも合わせて確認してください。
