【Oracle】MERGE文(UPSERT)完全ガイド|WHEN MATCHED/NOT MATCHED・条件付きMERGE・DELETE句・ORA-30926・実務パターンまで実例で解説

【Oracle】MERGE文(UPSERT)完全ガイド|WHEN MATCHED/NOT MATCHED・条件付きMERGE・DELETE句・ORA-30926・実務パターンまで実例で解説 Oracle

Oracle の MERGE 文は、条件に応じて INSERT と UPDATE(または DELETE)を1つの SQL で行える構文です。「あれば UPDATE、なければ INSERT」という処理(UPSERT)を、SELECT → IF → INSERT/UPDATE という複数ステップなしに1文で実現できます。

MERGE は SQL:2003 標準に準拠しており、Oracle 9i から利用可能です。Oracle では標準に加え、条件付き MERGEDELETE 句など独自の拡張もあります。

この記事でわかること

  • MERGE 文の基本構文(WHEN MATCHED / WHEN NOT MATCHED)
  • UPDATE のみ・INSERT のみの MERGE パターン
  • 条件付き MERGE(WHEN MATCHED AND … / WHEN NOT MATCHED AND …)
  • Oracle 独自の MERGE DELETE 句(不要行を UPDATE と同時に削除)
  • ORA-30926(ソーステーブルに重複がある場合)の原因と対処
  • 在庫更新・マスター同期・差分ロードの実務パターン
  • UPDATE + INSERT 個別実行との比較
スポンサーリンク

MERGE 文の基本構文

MERGE 文の基本構文
MERGE INTO ターゲット表 t          -- 更新・挿入先
USING ソース表またはサブクエリ s   -- 比較元データ
ON (結合条件)                      -- マッチング条件(主キー等)

WHEN MATCHED THEN
    -- 条件が一致した行: UPDATE または DELETE
    UPDATE SET
        t.列1 = s.列1,
        t.列2 = s.列2

WHEN NOT MATCHED THEN
    -- 条件が一致しなかった行: INSERT
    INSERT (列1, 列2, 列3)
    VALUES (s.列1, s.列2, s.列3);

MERGE 文の各要素の役割は次のとおりです。

説明
MERGE INTO 変更先のテーブル(ターゲット)を指定
USING 比較元のデータ(テーブル・ビュー・サブクエリ)を指定
ON マッチング条件。通常は主キーで結合する
WHEN MATCHED ON 条件が一致した行への操作(UPDATE / DELETE)
WHEN NOT MATCHED ON 条件が一致しなかった行への操作(INSERT)

MERGE の基本例:在庫テーブルの更新と新規追加

MERGE の基本例:在庫更新 UPSERT
-- 入荷データ(ソース)を使って在庫テーブル(ターゲット)を更新する
-- 既存商品: 在庫数を加算 / 新商品: 新規 INSERT

MERGE INTO inventory t              -- ターゲット: 在庫テーブル
USING (
    -- ソース: 入荷データ(サブクエリも使える)
    SELECT product_id, quantity, unit_price
    FROM incoming_shipment
    WHERE shipment_date = TRUNC(SYSDATE)
) s
ON (t.product_id = s.product_id)   -- 商品IDでマッチング

WHEN MATCHED THEN
    -- 既存商品: 在庫を加算・単価を更新
    UPDATE SET
        t.stock_qty  = t.stock_qty + s.quantity,
        t.unit_price = s.unit_price,
        t.updated_at = SYSDATE

WHEN NOT MATCHED THEN
    -- 新商品: 新規登録
    INSERT (product_id, stock_qty, unit_price, created_at)
    VALUES (s.product_id, s.quantity, s.unit_price, SYSDATE);

-- 実行後: SQL%ROWCOUNT で処理件数を確認
-- COMMIT を忘れずに
COMMIT;

UPDATE のみ・INSERT のみの MERGE

WHEN MATCHEDWHEN NOT MATCHED はどちらか一方だけ書くことができます。

WHEN MATCHED のみ:条件に一致した行だけ UPDATE
-- ターゲットに存在する行のみ更新(INSERT はしない)
MERGE INTO employees t
USING (
    SELECT employee_id, new_salary
    FROM salary_revision
) s
ON (t.employee_id = s.employee_id)

WHEN MATCHED THEN
    UPDATE SET t.salary = s.new_salary
    -- WHEN NOT MATCHED は省略: ソースにあってターゲットにない行は無視
;
COMMIT;
WHEN NOT MATCHED のみ:存在しない行だけ INSERT
-- 差分挿入: ターゲットに存在しないデータだけを INSERT する
-- (INSERT IGNORE / INSERT OR IGNORE の代替)
MERGE INTO products t
USING (
    SELECT product_id, product_name, category
    FROM new_products_staging
) s
ON (t.product_id = s.product_id)

WHEN NOT MATCHED THEN
    INSERT (product_id, product_name, category, created_at)
    VALUES (s.product_id, s.product_name, s.category, SYSDATE)
    -- WHEN MATCHED は省略: 既存行は変更しない
;
COMMIT;

条件付き MERGE:AND 句で対象を絞り込む

Oracle では WHEN MATCHED AND 条件 / WHEN NOT MATCHED AND 条件 のように、各句にさらに条件を追加できます。UPDATE する行を特定の条件でさらに絞り込む場合に使います。

条件付き MERGE:変更がある場合のみ UPDATE
-- 実際に値が変わった場合のみ UPDATE する(不要な UPDATE を削減)
MERGE INTO products t
USING products_staging s
ON (t.product_id = s.product_id)

WHEN MATCHED AND (
    t.product_name != s.product_name OR
    t.unit_price   != s.unit_price   OR
    t.category     != s.category
) THEN
    UPDATE SET
        t.product_name = s.product_name,
        t.unit_price   = s.unit_price,
        t.category     = s.category,
        t.updated_at   = SYSDATE

WHEN NOT MATCHED AND s.is_active = 1 THEN
    -- アクティブな商品のみ INSERT する
    INSERT (product_id, product_name, unit_price, category, created_at)
    VALUES (s.product_id, s.product_name, s.unit_price, s.category, SYSDATE);
COMMIT;

Oracle 独自の MERGE DELETE 句

Oracle では WHEN MATCHED THEN UPDATE ... DELETE WHERE ... という構文で、UPDATE の後に条件を満たす行を削除できます。「フラグが無効になったレコードをターゲットから除去する」処理を1文で書けます。

MERGE DELETE 句:無効化された行をターゲットから削除
-- ソースの is_active = 0 の行は UPDATE した後に DELETE する
MERGE INTO products t
USING products_staging s
ON (t.product_id = s.product_id)

WHEN MATCHED THEN
    UPDATE SET
        t.product_name = s.product_name,
        t.unit_price   = s.unit_price,
        t.is_active    = s.is_active
    DELETE WHERE t.is_active = 0   -- UPDATE 後に is_active=0 の行を削除

WHEN NOT MATCHED AND s.is_active = 1 THEN
    INSERT (product_id, product_name, unit_price, is_active)
    VALUES (s.product_id, s.product_name, s.unit_price, 1);
COMMIT;

-- 注意: DELETE は WHEN MATCHED THEN UPDATE とセットでしか使えない
-- DELETE 単独の WHEN MATCHED THEN DELETE という構文は存在しない

ORA-30926: ソース行が重複している場合のエラー

MERGE の ON 条件に対してソーステーブルに重複する行が存在すると、ORA-30926: ソース表の安定した行セットを取得できません が発生します。1つのターゲット行に対してどのソース行で UPDATE するかが一意に決められないためです。

ORA-30926 の原因と対処
-- NG: ソースに product_id=100 が2行あると ORA-30926 が発生する
MERGE INTO products t
USING products_staging s   -- product_id=100 が重複して存在
ON (t.product_id = s.product_id)
WHEN MATCHED THEN
    UPDATE SET t.unit_price = s.unit_price;
-- → ORA-30926: ソース表の安定した行セットを取得できません

-- OK: ソースを集約して重複を排除する
MERGE INTO products t
USING (
    -- サブクエリで重複を排除(最新の unit_price を優先)
    SELECT product_id,
           MAX(unit_price) AS unit_price   -- または MIN、または ROW_NUMBER() で1件に絞る
    FROM products_staging
    GROUP BY product_id
) s
ON (t.product_id = s.product_id)
WHEN MATCHED THEN
    UPDATE SET t.unit_price = s.unit_price;

-- ROW_NUMBER を使って最新1件に絞る方法
MERGE INTO products t
USING (
    SELECT product_id, unit_price
    FROM (
        SELECT product_id, unit_price,
               ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY updated_at DESC) AS rn
        FROM products_staging
    )
    WHERE rn = 1
) s
ON (t.product_id = s.product_id)
WHEN MATCHED THEN
    UPDATE SET t.unit_price = s.unit_price;

実務パターン:マスターデータの差分同期

外部システムからロードしたステージングテーブルを使って、マスターテーブルを差分同期する典型的なパターンです。

差分同期プロシージャ(MERGE + エラーハンドリング)
CREATE OR REPLACE PROCEDURE sync_products AS
    v_start_time TIMESTAMP := SYSTIMESTAMP;
    v_rows_merged NUMBER := 0;
BEGIN
    -- ステージングテーブルのデータを本番テーブルに同期
    MERGE INTO products t
    USING (
        -- ソースを集約して重複を排除(ORA-30926 対策)
        SELECT product_id,
               MAX(product_name) KEEP (DENSE_RANK LAST ORDER BY load_seq) AS product_name,
               MAX(unit_price)   KEEP (DENSE_RANK LAST ORDER BY load_seq) AS unit_price,
               MAX(category)     KEEP (DENSE_RANK LAST ORDER BY load_seq) AS category,
               MAX(is_active)    KEEP (DENSE_RANK LAST ORDER BY load_seq) AS is_active
        FROM products_staging
        WHERE load_date = TRUNC(SYSDATE)
        GROUP BY product_id
    ) s
    ON (t.product_id = s.product_id)

    WHEN MATCHED AND (
        t.product_name != s.product_name OR
        t.unit_price   != s.unit_price   OR
        t.category     != s.category     OR
        t.is_active    != s.is_active
    ) THEN
        UPDATE SET
            t.product_name = s.product_name,
            t.unit_price   = s.unit_price,
            t.category     = s.category,
            t.is_active    = s.is_active,
            t.updated_at   = SYSDATE
        DELETE WHERE t.is_active = 0

    WHEN NOT MATCHED AND s.is_active = 1 THEN
        INSERT (product_id, product_name, unit_price, category, is_active, created_at)
        VALUES (s.product_id, s.product_name, s.unit_price,
                s.category, 1, SYSDATE);

    v_rows_merged := SQL%ROWCOUNT;
    COMMIT;

    DBMS_OUTPUT.PUT_LINE(
        '同期完了: ' || v_rows_merged || '件, 処理時間: ' ||
        EXTRACT(SECOND FROM (SYSTIMESTAMP - v_start_time)) || '秒'
    );

EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('エラー: ' || SQLERRM);
        RAISE;
END sync_products;
/

MERGE vs INSERT + UPDATE 個別実行の比較

観点 MERGE 文 SELECT → INSERT/UPDATE 個別実行
SQL 文の数 1文 複数文(SELECT + IF + INSERT/UPDATE)
パフォーマンス テーブルスキャンが1回 SELECT と INSERT/UPDATE で2回以上
競合状態(TOCTOU) 原子的に処理されるため安全 SELECT 後に別セッションが INSERT → 重複の可能性
コードの可読性 ロジックが1か所に集中 条件分岐が多いと読みにくくなる
ソース重複時 ORA-30926 が発生(対処が必要) 件数分ループで処理できる
MERGE が特に有効なケース

  • ステージングテーブル → 本番テーブルの差分同期(バッチ処理)
  • 外部システムからの定期ロード(CSVインポートなど)
  • 在庫・残高など数値の加算更新と新規追加が混在する場合

まとめ

  • MERGE INTO … USING … ON で「あれば UPDATE、なければ INSERT」を1文で実現
  • WHEN MATCHED AND 条件 で更新対象をさらに絞り込める
  • Oracle 独自の DELETE WHERE 句で UPDATE + 不要行削除を同時実行
  • ORA-30926 が出た場合は USING 句でソースを集約して重複を排除する
  • MERGE はテーブルスキャンが1回で済み、TOCTOU 競合も防げるため、バッチ同期に最適

MERGE と密接に関連する UNIQUE 制約違反(ORA-00001)の対処についてはORA-00001 完全ガイドを、MERGE を PL/SQL プロシージャに組み込む方法はストアドプロシージャ・ファンクションの作成完全ガイドも参照してください。