【Oracle】SQL*Loader(sqlldr)完全ガイド|コントロールファイル・APPEND/REPLACE・ダイレクトパスロード・BADファイル・エラー対処まで解説

【Oracle】SQL*Loader(sqlldr)完全ガイド|コントロールファイル・APPEND/REPLACE・ダイレクトパスロード・BADファイル・エラー対処まで解説 Oracle

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 を使います。

区切り文字・囲み文字・固定長の指定

タブ区切り・パイプ区切り・固定長のコントロールファイル例
-- タブ区切りの場合
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)完全ガイドも合わせて確認してください。