Postgres UPSERT (INSERT or UPDATE) only if value is different

前端 未结 6 1559
你的背包
你的背包 2021-02-05 17:26

I\'m updating a Postgres 8.4 database (from C# code) and the basic task is simple enough: either UPDATE an existing row or INSERT a new one if one doesn\'t exist yet. Normally I

6条回答
  •  予麋鹿
    予麋鹿 (楼主)
    2021-02-05 17:36

    The RETURNING clause enables you to chain your queries; the second query uses the results from the first. (in this case to avoid re-touching the same rows) (RETURNING is available since postgres 8.4)

    Shown here embedded in a a function, but it works for plain SQL, too

    DROP SCHEMA tmp CASCADE;
    CREATE SCHEMA tmp ;
    SET search_path=tmp;
    
    CREATE TABLE my_table
            ( updated_time timestamp NOT NULL DEFAULT now()
            , updated_username varchar DEFAULT '_none_'
            , criteria1 varchar NOT NULL
            , criteria2 varchar NOT NULL
            , value1 varchar
            , value2 varchar
            , PRIMARY KEY (criteria1,criteria2)
            );
    
    INSERT INTO  my_table (criteria1,criteria2,value1,value2)
    SELECT 'C1_' || gs::text
            , 'C2_' || gs::text
            , 'V1_' || gs::text
            , 'V2_' || gs::text
    FROM generate_series(1,10) gs
            ;
    
    SELECT * FROM my_table ;
    
    CREATE function funky(_criteria1 text,_criteria2 text, _newvalue1 text, _newvalue2 text)
    RETURNS VOID
    AS $funk$
    WITH ins AS (
            INSERT INTO my_table(criteria1, criteria2, value1, value2, updated_username)
            SELECT $1, $2, $3, $4, COALESCE(current_user, 'evgeny' )
            WHERE NOT EXISTS (
                    SELECT * FROM my_table nx
                    WHERE nx.criteria1 = $1 AND nx.criteria2 = $2
                    )
            RETURNING criteria1 AS criteria1, criteria2 AS criteria2
            )
            UPDATE my_table upd
            SET value1 = $3, value2 = $4
            , updated_time = now()
            , updated_username = COALESCE(current_user, 'evgeny')
            WHERE 1=1
            AND criteria1 = $1 AND criteria2 = $2 -- key-condition
            AND (value1 <> $3 OR value2 <> $4 )   -- row must have changed
            AND NOT EXISTS (
                    SELECT * FROM ins -- the result from the INSERT
                    WHERE ins.criteria1 = upd.criteria1
                    AND ins.criteria2 = upd.criteria2
                    )
            ;
    $funk$ language sql
            ;
    
    SELECT funky('AA', 'BB' , 'CC', 'DD' );            -- INSERT
    SELECT funky('C1_3', 'C2_3' , 'V1_3', 'V2_3' );    -- (null) UPDATE 
    SELECT funky('C1_7', 'C2_7' , 'V1_7', 'V2_7777' ); -- (real) UPDATE 
    
    SELECT * FROM my_table ;
    

    RESULT:

            updated_time        | updated_username | criteria1 | criteria2 | value1 | value2  
    ----------------------------+------------------+-----------+-----------+--------+---------
     2013-03-13 16:37:55.405267 | _none_           | C1_1      | C2_1      | V1_1   | V2_1
     2013-03-13 16:37:55.405267 | _none_           | C1_2      | C2_2      | V1_2   | V2_2
     2013-03-13 16:37:55.405267 | _none_           | C1_3      | C2_3      | V1_3   | V2_3
     2013-03-13 16:37:55.405267 | _none_           | C1_4      | C2_4      | V1_4   | V2_4
     2013-03-13 16:37:55.405267 | _none_           | C1_5      | C2_5      | V1_5   | V2_5
     2013-03-13 16:37:55.405267 | _none_           | C1_6      | C2_6      | V1_6   | V2_6
     2013-03-13 16:37:55.405267 | _none_           | C1_8      | C2_8      | V1_8   | V2_8
     2013-03-13 16:37:55.405267 | _none_           | C1_9      | C2_9      | V1_9   | V2_9
     2013-03-13 16:37:55.405267 | _none_           | C1_10     | C2_10     | V1_10  | V2_10
     2013-03-13 16:37:55.463651 | postgres         | AA        | BB        | CC     | DD
     2013-03-13 16:37:55.472783 | postgres         | C1_7      | C2_7      | V1_7   | V2_7777
    (11 rows)
    

提交回复
热议问题