Postgres UPSERT(INSERT或UPDATE)仅在值不同时才使用

EMP*_*EMP 13 sql postgresql merge upsert

我正在更新Postgres 8.4数据库(来自C#代码),基本任务很简单:要么更新现有行,要么插入一个新行(如果还不存在).通常我会这样做:

UPDATE my_table
SET value1 = :newvalue1, ..., updated_time = now(), updated_username = 'evgeny'
WHERE criteria1 = :criteria1 AND criteria2 = :criteria2
Run Code Online (Sandbox Code Playgroud)

如果0行受到影响,那么执行INSERT:

INSERT INTO my_table(criteria1, criteria2, value1, ...)
VALUES (:criteria1, :criteria2, :newvalue1, ...)
Run Code Online (Sandbox Code Playgroud)

但是有一点点扭曲.我不想更改updated_timeupdated_username列,除非任何新值实际上与现有值不同,以避免误导用户更新数据的时间.

如果我只是在进行UPDATE,那么我也可以为这些值添加WHERE条件,但这在这里不起作用,因为如果DB已经是最新的UPDATE将影响0行,那么我会尝试INSERT.

任何人都可以想到一个优雅的方法来做到这一点,除了SELECT,然后更新或插入?

Fra*_*ens 6

看一下BEFORE UPDATE触发器以检查和设置正确的值:

CREATE OR REPLACE FUNCTION my_trigger() RETURNS TRIGGER LANGUAGE plpgsql AS
$$
BEGIN
    IF OLD.content = NEW.content THEN
        NEW.updated_time= OLD.updated_time; -- use the old value, not a new one.
    ELSE
        NEW.updated_time= NOW();
    END IF;
    RETURN NEW;
END;
$$;
Run Code Online (Sandbox Code Playgroud)

现在,您甚至不必在UPDATE查询中提及字段update_time,它将由触发器处理。

http://www.postgresql.org/docs/current/interactive/plpgsql-trigger.html


Gav*_*vin 6

这里有两件事.首先,根据数据库中的活动级别,您可能会在检查记录和将其插入到另一个进程可能在此期间创建该记录的位置之间遇到竞争条件.本手册包含如何执行此 链接示例的示例

为了避免进行更新,有suppress_redundant_updates_trigger()过程.要使用此功能,您必须在更新触发器之前有两个,第一个将调用suppress_redundant_updates_trigger()以在未进行更改时中止更新,第二个将在更新时设置时间戳和用户名.触发器按字母顺序触发.这样做也意味着更改上面示例中的代码,以便在更新之前首先尝试插入.

抑制更新如何工作的示例:

    DROP TABLE sru_test;

    CREATE TABLE sru_test(id integer not null primary key,
    data text,
    updated timestamp(3));

    CREATE TRIGGER z_min_update
    BEFORE UPDATE ON sru_test
    FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();

    DROP FUNCTION set_updated();

    CREATE FUNCTION set_updated()
    RETURNS TRIGGER
    AS $$
    DECLARE
    BEGIN
        NEW.updated := now();
        RETURN NEW;
    END;
    $$ LANGUAGE plpgsql;

    CREATE TRIGGER zz_set_updated
    BEFORE INSERT OR UPDATE ON sru_test
    FOR EACH ROW EXECUTE PROCEDURE  set_updated();

insert into sru_test(id,data) VALUES (1,'Data 1');
insert into sru_test(id,data) VALUES (2,'Data 2');

select * from sru_test;

update sru_test set data = 'NEW';

select * from sru_test;

update sru_test set data = 'NEW';

select * from sru_test;

update sru_test set data = 'ALTERED'  where id = 1;

select * from sru_test;

update sru_test set data = 'NEW' where id = 2;

select * from sru_test;
Run Code Online (Sandbox Code Playgroud)


Tob*_*ouw -1

开始交易。使用 select 来查看要插入的数据是否已经存在,如果存在,则不执行任何操作,否则更新,如果不存在,则插入。最后关闭交易。

  • 这是次优的,因为常见的用例可能是在事务中批量插入一堆行 (2认同)