4

既存のデータに応じて、UPDATE または INSERT クエリを実行する「マージ」関数を作成しました。(利用可能なほとんどの例のように) テーブルごとに upsert-wrapper を記述する代わりに、この関数は SQL 文字列全体を受け取ります。どちらの SQL 文字列も、アプリケーションによって自動的に生成されます。

計画は、次のように関数を呼び出すことです。

-- hypothetical "settings" table, with a primary key of (user_id, setting):

SELECT merge(
    $$UPDATE settings SET value = 'x' WHERE user_id = 42 AND setting = 'foo'$$,
    $$INSERT INTO settings (user_id, setting, value) VALUES (42, 'foo', 'x')$$
);

以下は、merge() 関数の完全なコードです。

CREATE OR REPLACE FUNCTION merge (update_sql TEXT, insert_sql TEXT) RETURNS TEXT AS
$func$
DECLARE
    max_iterations INTEGER := 10;
    i INTEGER := 0;
    num_updated INTEGER;
BEGIN
    -- usually returns before re-entering the loop
    LOOP

        -- first try the update
        EXECUTE update_sql;
        GET DIAGNOSTICS num_updated = ROW_COUNT;
        IF num_updated > 0 THEN
            RETURN 'UPDATE';
        END IF;

        -- nothing was updated: try the insert, watching out for concurrent inserts
        BEGIN
            EXECUTE insert_sql;
            RETURN 'INSERT';
        EXCEPTION WHEN unique_violation THEN
            -- nop; just loop and try again from the top
        END;

        -- emergency brake
        i := i + 1;
        IF i >= max_iterations THEN
            RAISE EXCEPTION 'merge(): tried looping % times, giving up now.', i;
            EXIT;
        END IF;

    END LOOP;
END;
$func$
LANGUAGE plpgsql;

私のテストでは十分に機能しているように見えますが、特にこの関数を使用せずに発行される可能性のある同時 UPDATE/INSERT/DELETE クエリに関して、重要なことを見逃していないかどうかはわかりません。何か重要なことを見落としていませんか?

この機能について私が参考にしたリソースには次のものがあります。

(編集: 目標の 1 つは、ターゲット テーブルのロックを回避することでした。)

4

1 に答える 1

1

あなたの質問に対する答えは、アプリケーションがデータベースにアクセスする方法のコンテキストによって異なります。あなたが引用した depesz の投稿でうまく説明されているように、これを解決する方法はたくさんあります。さらに、書き込み可能な CTE の使用を検討することもできます。ここを参照してください。[質問]挿入、PostgreSQL での重複更新についても? 意思決定プロセスに関する興味深い議論があります。

于 2013-09-26T20:33:59.220 に答える