PL/SQLでテーブルを同期する

他システムなど外部からデータを取り込み加工して本テーブルに取り込みたい場合というのは非常に多い。非常に多いにも関わらず、SQLでこのような処理を書くのは意外と難しい。いろいろと試行錯誤した結果、FULL JOINを使う方法に落ち着いた。

FOR cur_rec IN (
    SELECT
        S.キー1,
        S.キー2,
        S.値1,
        S.値2,
        S.値3,
        D.UPDATE_ROWID,
        (CASE
            WHEN D.キー1 IS NULL THEN 'I' -- 登録
            WHEN S.キー1 IS NULL THEN 'D' -- 削除
            WHEN S.値1 || 'X' <> D.値1 || 'X'
                OR S.値2 || 'X' <> D.値2 || 'X'
                OR S.値3 || 'X' <> D.値3 || 'X'
                THEN 'U' -- 更新
        END) AS UPDATE_STATE
    FROM
        (
            -- 同期したいデータセットを取得するSQLを書く
            SELECT
                DMT1.キー1,
                DMT2.キー2,
                DMT1.値1,
                DMT1.値2,
                DMT2.値3
            FROM
               同期元テーブル1 DMT1
               INNER JOIN 同期元テーブル2 DMT2
                  ON ...
            WHERE
               ...
        ) S
        FULL JOIN (
            -- 同期するデータセットを取得するSQLを書く
            SELECT
                DST.ROWID AS UPDATE_ROWID,
                DST.キー1,
                DST.キー2,
                DST.値1,
                DST.値2,
                DST.値3
            FROM
                同期先テーブル DST
        ) D
            ON D.キー1 = S.キー1
            AND D.キー2 = S.キー2
) LOOP
    IF cur_rec.UPDATE_STATE = 'I' THEN
        INSERT INTO 同期先テーブル (
            ...
        ) VALUE (
            ...
        );
    ELSIF cur_rec.UPDATE_STATE = 'U' THEN
        UPDATE 同期先テーブル
        SET
            ...
        WHERE
            ROWID = cur_rec.UPDATE_ROWID;
    ELSIF cur_rec.UPDATE_STATE = 'D' THEN
        DELETE FROM 同期先テーブル
        WHERE
            ROWID = cur_rec.UPDATE_ROWID;
    END IF;
END LOOP;

この手の処理では、SELECT INSERT や MERGE 句が使われることが多いが、ログ出力や存在チェックなどを考えるとこちらの書き方の方が柔軟だと思う。