通常の INSERT 文は 1 つのテーブルにしか挿入できません。マルチテーブル INSERT を使うと、1 つの SELECT の結果を同時に複数のテーブルに挿入できます。
ETL 処理でステージングテーブルのデータを複数のターゲットテーブルに振り分けたり、DWH のファクトテーブルとディメンションテーブルを同時にロードしたりする場面で有用です。複数の INSERT 文を個別に実行するより処理が速く、データの一貫性を保ちやすいメリットがあります。
この記事でわかること
- INSERT ALL(無条件)で全行を複数テーブルに同時挿入する方法
- 条件付き INSERT ALL(WHEN〜THEN〜INTO)で条件に応じて挿入先テーブルを振り分ける方法
- INSERT FIRST で最初に一致した条件のテーブルにのみ挿入する方法
- INSERT ALL と INSERT FIRST の使い分け
- ETL でよく使うパターン(ステージング→ファクト/ディメンション同時ロード)
- シーケンスを使う際の注意点と制限事項
INSERT ALL(無条件)で複数テーブルに同時挿入する
INSERT ALL は SELECT の各行を、記述した全 INTO 句のテーブルに挿入します。1 行のソースデータが N 個の INTO 句に対して N 回挿入されます。
INSERT ALL の基本構文
-- INSERT ALL の基本: SELECT の全行を全ての INTO に挿入する
-- 例: ステージングテーブルのデータを sales と sales_audit に同時に挿入する
INSERT ALL
INTO sales (order_id, customer_id, product_id, sale_date, amount)
VALUES (order_id, customer_id, product_id, sale_date, amount)
INTO sales_audit (order_id, action, action_date, amount)
VALUES (order_id, 'INSERT', SYSDATE, amount)
SELECT order_id, customer_id, product_id, sale_date, amount
FROM sales_staging
WHERE status = 'READY';
COMMIT;
-- SELECT の列値を複数の INSERT に使い回せる
-- 1行のソース → 複数テーブルに1行ずつ挿入される
-- 1000行の SELECT → sales に 1000行 + sales_audit に 1000行 = 計 2000行 INSERT される
-- ターゲットテーブルのデフォルト値を使う場合は VALUES を省略できる
-- ただし列数と型が一致している必要がある
INSERT ALL
INTO orders_copy
INTO orders_history
SELECT * FROM orders WHERE order_date < ADD_MONTHS(SYSDATE, -12);
-- SELECT * の列が orders_copy と orders_history に同じ順で定義されている場合
条件付き INSERT ALL(WHEN〜THEN〜INTO)で挿入先を振り分ける
WHEN 条件 THEN INTO テーブル を使うと、行の内容に応じて挿入先テーブルを振り分けられます。条件付き INSERT ALL は、1 行のソースデータが複数の WHEN 条件に一致した場合、一致した全条件のテーブルに挿入される点が INSERT FIRST と異なります。
条件付き INSERT ALL で挿入先を振り分ける
-- 条件付き INSERT ALL: 各 WHEN 条件が独立して評価される
-- 1 行が複数の WHEN に一致した場合、一致した全テーブルに挿入される
INSERT ALL
WHEN amount >= 100000 THEN
INTO sales_large (order_id, customer_id, amount, sale_date)
VALUES (order_id, customer_id, amount, sale_date)
WHEN amount >= 10000 AND amount < 100000 THEN
INTO sales_medium (order_id, customer_id, amount, sale_date)
VALUES (order_id, customer_id, amount, sale_date)
WHEN amount < 10000 THEN
INTO sales_small (order_id, customer_id, amount, sale_date)
VALUES (order_id, customer_id, amount, sale_date)
WHEN region = 'EAST' THEN
INTO sales_east_region (order_id, amount)
VALUES (order_id, amount)
SELECT order_id, customer_id, amount, sale_date, region
FROM sales_staging
WHERE status = 'READY';
-- amount=50000、region='EAST' の行は:
-- → sales_medium に挿入される(2番目のWHEN一致)
-- → sales_east_region に挿入される(4番目のWHEN一致)
-- → sales_large と sales_small には挿入されない(条件不一致)
INSERT FIRST で最初に一致した条件のテーブルにのみ挿入する
INSERT FIRST は WHEN〜THEN〜INTO 条件を上から評価して、最初に一致した条件のテーブルにのみ挿入します。残りの WHEN 条件は評価されません。排他的な振り分けが必要な場合に使います。
INSERT FIRST で排他的に振り分ける
-- INSERT FIRST: 最初に一致した WHEN だけに挿入する(CASE 文の WHEN に近い動作)
INSERT FIRST
WHEN amount >= 100000 THEN
INTO sales_large (order_id, customer_id, amount, sale_date)
VALUES (order_id, customer_id, amount, sale_date)
WHEN amount >= 50000 THEN
INTO sales_medium (order_id, customer_id, amount, sale_date)
VALUES (order_id, customer_id, amount, sale_date)
WHEN amount >= 10000 THEN
INTO sales_small_medium (order_id, customer_id, amount, sale_date)
VALUES (order_id, customer_id, amount, sale_date)
ELSE
INTO sales_micro (order_id, customer_id, amount, sale_date)
VALUES (order_id, customer_id, amount, sale_date)
SELECT order_id, customer_id, amount, sale_date
FROM sales_staging
WHERE status = 'READY';
-- amount=75000 の行:
-- → 1番目の WHEN (>=100000) は不一致 → スキップ
-- → 2番目の WHEN (>=50000) は一致 → sales_medium に挿入、以降の WHEN は評価しない
-- INSERT ALL ではなく INSERT FIRST なので sales_small_medium には挿入されない
-- ELSE 句で全条件不一致時の挿入先を指定できる(省略した場合は挿入されない)
INSERT ALL と INSERT FIRST の使い分け
| 項目 | INSERT ALL(条件付き) | INSERT FIRST |
|---|---|---|
| 条件評価 | 全 WHEN 条件を独立して評価する | 上から順に評価して最初に一致したら終了 |
| 1 行の挿入先 | 一致した全テーブルに挿入(複数 OK) | 最初に一致した 1 テーブルにのみ挿入 |
| 条件の重複 | 条件が重複していても両方に挿入される | 条件が重複していても最初の一致のみ |
| ELSE 句 | なし | ELSE 句で全不一致時の挿入先を指定できる |
| 使用場面 | 1 行を複数テーブルに同時配信したい場合 | データを排他的に振り分けたい場合(分類) |
ETL でよく使うパターン
ステージングテーブルからファクト・ディメンションを同時ロードする
-- DWH の典型的な ETL パターン:
-- orders_staging からファクトテーブル(order_facts)とディメンション(order_status_dim)に同時ロード
INSERT ALL
-- ファクトテーブルに挿入する(数値・外部キーを格納)
INTO order_facts (
order_id, customer_id, product_id, region_id,
order_date, amount, quantity
)
VALUES (
order_id, customer_id, product_id, region_id,
order_date, amount, quantity
)
-- ステータス変更ログテーブルに挿入する
INTO order_status_log (
order_id, status, logged_at
)
VALUES (
order_id, status, SYSDATE
)
-- キャンセルされた注文のみキャンセルテーブルにも挿入する
WHEN status = 'CANCELLED' THEN
INTO cancelled_orders (order_id, customer_id, amount, cancelled_at)
VALUES (order_id, customer_id, amount, SYSDATE)
SELECT
stg.order_id,
stg.customer_id,
stg.product_id,
NVL(r.region_id, 99) AS region_id,
stg.order_date,
stg.amount,
stg.quantity,
stg.status
FROM orders_staging stg
LEFT JOIN regions r ON stg.region_code = r.region_code
WHERE stg.loaded_flag = 'N';
COMMIT;
-- ステージングのフラグを更新して二重ロードを防ぐ
UPDATE orders_staging SET loaded_flag = 'Y'
WHERE loaded_flag = 'N';
COMMIT;
マルチテーブル INSERT の注意事項
シーケンスを使う際の注意点
マルチテーブル INSERT の SELECT 句でシーケンス(
マルチテーブル INSERT の SELECT 句でシーケンス(
seq.NEXTVAL)を使うと、各 INTO 句の挿入ごとにシーケンスが1回ずつ採番されるのではなく、SELECT の各行に対して1回だけ採番されます。つまり全 INTO 句で同じ NEXTVAL が共有されます。複数テーブルに異なるシーケンス値を割り当てたい場合は、PL/SQL で個別に INSERT するか、仮想列で計算する必要があります。
マルチテーブル INSERT の制限と回避策
-- マルチテーブル INSERT でシーケンスを使う場合の注意
-- SELECT 句で seq.NEXTVAL を使うと全 INTO で同じ値が使われる
INSERT ALL
INTO table_a (id, name) VALUES (new_id, name)
INTO table_b (id, ref_id) VALUES (seq_b.NEXTVAL, new_id) -- new_id は SELECT 句の値
SELECT seq_a.NEXTVAL AS new_id, name FROM source_table;
-- new_id: SELECT ごとに1回採番(全 INTO で共有される)
-- seq_b.NEXTVAL: INTO ごとに採番 → table_a と table_b で異なる値になる
-- マルチテーブル INSERT の主な制限事項:
-- ・RETURNING 句は使えない
-- ・サブクエリの UPDATE は不可(INSERT のみ)
-- ・Oracle では EXECUTE IMMEDIATE 内での使用はサポートされない
-- ・INSERT ALL は暗黙的にコミットしない(COMMIT が必要)
-- ・ビューへの INSERT はビューが更新可能である場合のみ可能
-- マルチテーブル INSERT のパフォーマンス:
-- 複数の個別 INSERT 文より1回のテーブルスキャンで済むため効率的
-- ただし各 INTO テーブルへのロック・インデックス更新は個別に発生する
まとめ
- INSERT ALL(無条件):SELECT の全行を記述した全テーブルに挿入する。1 行が N 個の INTO に対して N 回挿入される
- 条件付き INSERT ALL:各 WHEN 条件を独立して評価する。1 行が複数条件に一致した場合は複数テーブルに挿入される
- INSERT FIRST:最初に一致した WHEN 条件のテーブルにのみ挿入する。排他的な振り分けに使う。ELSE 句が使える
- ETL での活用:ステージングテーブルからファクト・ディメンション・ログテーブルへの同時ロードに有効。テーブルスキャンが1回で済む
- シーケンスの扱い:SELECT 句のシーケンスは行ごとに1回採番。INTO 句のシーケンスは INTO ごとに採番。混乱しやすいため注意
INSERT・UPDATE・DELETE の基本については Oracle DML 完全ガイドを参照してください。MERGE 文での条件付き INSERT/UPDATE の組み合わせは Oracle MERGE 完全ガイドも参照してください。