在PostgreSQL中重复更新时插入?

在PostgreSQL中重复更新时插入?,sql,postgresql,upsert,sql-merge,Sql,Postgresql,Upsert,Sql Merge,几个月前,我从一个关于堆栈溢出的回答中学到了如何使用以下语法在MySQL中一次执行多个更新: INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z) ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2); 我现在切换到PostgreSQL,显然这是不正确的。它指的是所有正确的表,所以我认为这是使用不同关键字的问题,但我不确定

几个月前,我从一个关于堆栈溢出的回答中学到了如何使用以下语法在MySQL中一次执行多个更新:

INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);
我现在切换到PostgreSQL,显然这是不正确的。它指的是所有正确的表,所以我认为这是使用不同关键字的问题,但我不确定PostgreSQL文档中在哪里介绍了这一点


为了澄清,我想插入一些内容,如果它们已经存在,则更新它们。

根据,不支持在重复键上处理
案例。这部分语法是一个专有的MySQL扩展。

没有简单的命令来执行

最正确的方法是使用函数,就像来自

另一个解决方案(虽然不是那么安全)是通过返回进行更新,检查哪些行是更新,然后插入其余的行

大致如下:

update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;
假设返回了id:2:

insert into table (id, column) values (1, 'aa'), (3, 'cc');
当然,它迟早会退出(在并发环境中),因为这里有明确的竞争条件,但通常它会工作


这里有一个。

PostgreSQL,因为9.5版有语法,带有子句。具有以下语法(类似于MySQL)


在postgresql的电子邮件组存档中搜索“upsert”可以找到:

示例38-2。更新/插入的例外情况

此示例使用异常处理执行更新或插入(视情况而定):


可能有一个示例说明了如何使用9.1及以上版本中的CTE批量执行此操作:


有关更清晰的示例,请参见。

有关合并小集合的信息,可以使用上述函数。但是,如果您要合并大量数据,我建议您

据我所知,目前的最佳实践是:

  • 将新的/更新的数据复制到临时表中(当然,如果成本合适,也可以进行插入)
  • 获取锁[可选](建议优先于表锁,IMO)
  • 合并。(有趣的部分)

  • 当我来到这里的时候,我也在寻找同样的东西,但是缺少一个通用的“upsert”函数让我有点困扰,所以我想你可以在手册中传递更新并插入sql作为该函数的参数

    看起来是这样的:

    CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
        RETURNS VOID
        LANGUAGE plpgsql
    AS $$
    BEGIN
        LOOP
            -- first try to update
            EXECUTE sql_update;
            -- check if the row is found
            IF FOUND THEN
                RETURN;
            END IF;
            -- not found so insert the row
            BEGIN
                EXECUTE sql_insert;
                RETURN;
                EXCEPTION WHEN unique_violation THEN
                    -- do nothing and loop
            END;
        END LOOP;
    END;
    $$;
    
    CREATE RULE replace_dns AS 
        ON INSERT TO dns 
        WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
                AND (dns.customer_id = new.customer_id)))) 
        DO INSTEAD UPDATE dns 
            SET hits = new.hits 
            WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));
    
    也许要做您最初想要做的事情,批处理“upsert”,您可以使用Tcl拆分sql_更新并循环单个更新,性能影响将非常小,请参见


    最高的成本是从代码中执行查询,在数据库端,执行成本要小得多。警告:如果同时从多个会话执行,这是不安全的(请参阅下面的注意事项)


    在postgresql中执行“UPSERT”的另一个聪明方法是执行两个连续的UPDATE/INSERT语句,每个语句都设计为成功或无效

    UPDATE table SET field='C', field2='Z' WHERE id=3;
    INSERT INTO table (id, field, field2)
           SELECT 3, 'C', 'Z'
           WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);
    
    如果已经存在“id=3”的行,则更新将成功,否则将无效

    UPDATE table SET field='C', field2='Z' WHERE id=3;
    INSERT INTO table (id, field, field2)
           SELECT 3, 'C', 'Z'
           WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);
    
    只有当“id=3”的行不存在时,插入才会成功

    您可以将这两个字符串组合成一个字符串,并使用从应用程序执行的单个SQL语句来运行它们。强烈建议在单个事务中同时运行它们

    这在隔离运行或在锁定表上运行时效果很好,但会受到竞争条件的影响,这意味着如果同时插入一行,它仍可能失败并出现重复键错误,或者在同时删除一行时,它可能终止而不插入任何行。PostgreSQL 9.1或更高版本上的
    SERIALIZABLE
    事务将以极高的序列化失败率为代价可靠地处理该事务,这意味着您必须多次重试。请参阅,其中更详细地讨论了此案例

    如果要插入和替换以下内容,此方法也是。

    I上面的自定义“upsert”函数:

    `

    在执行之后,执行以下操作:

    SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)
    
    放置双美元逗号以避免编译器错误很重要

    • 检查速度
    我在管理帐户设置时遇到了与名称-值对相同的问题。 设计标准是不同的客户端可以有不同的设置集

    我的解决方案类似于JWP,是批量擦除和替换,在应用程序中生成合并记录

    这是非常可靠的、独立于平台的,因为每个客户机的设置从来都不超过20个,所以只有3个相当低负载的db调用—可能是最快的方法

    更新单个行(检查异常然后插入)或某些组合的替代方法是可怕的代码,速度慢,并且经常中断,因为(如上所述)非标准SQL异常处理从db更改为db,甚至从一个版本更改为另一个版本

     #This is pseudo-code - within the application:
     BEGIN TRANSACTION - get transaction lock
     SELECT all current name value pairs where id = $id into a hash record
     create a merge record from the current and update record
      (set intersection where shared keys in new win, and empty values in new are deleted).
     DELETE all name value pairs where id = $id
     COPY/INSERT merged records 
     END TRANSACTION
    

    编辑:这不符合预期。与公认的答案不同,当两个进程同时重复调用
    upsert\u foo
    时,这会产生唯一的密钥冲突

    尤里卡!我在一个查询中找到了一种方法:使用
    UPDATE。。。返回
    以测试是否有任何行受到影响:

    CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);
    
    CREATE FUNCTION update_foo(k INT, v TEXT)
    RETURNS SETOF INT AS $$
        UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
    $$ LANGUAGE sql;
    
    CREATE FUNCTION upsert_foo(k INT, v TEXT)
    RETURNS VOID AS $$
        INSERT INTO foo
            SELECT $1, $2
            WHERE NOT EXISTS (SELECT update_foo($1, $2))
    $$ LANGUAGE sql;
    
    更新必须在单独的过程中完成,因为不幸的是,这是一个语法错误:

    ... WHERE NOT EXISTS (UPDATE ...)
    
    现在,它可以根据需要工作:

    SELECT upsert_foo(1, 'hi');
    SELECT upsert_foo(1, 'bye');
    SELECT upsert_foo(3, 'hi');
    SELECT upsert_foo(3, 'bye');
    

    对于PostgreSQL 9.1,这可以通过使用可写CTE()实现:

    请参阅以下博客:


    请注意,此解决方案不会防止唯一密钥冲突,但不会受到丢失更新的攻击。
    请参见

    本人,我已在insert语句后面设置了一条“规则”。假设您有一个“dns”表,记录每个客户每次的dns点击次数:

    CREATE TABLE dns (
        "time" timestamp without time zone NOT NULL,
        customer_id integer NOT NULL,
        hits integer
    );
    
    您希望能够使用更新的值重新插入行,或者在行不存在时创建它们。键入客户id和时间。大概是这样的:

    CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
        RETURNS VOID
        LANGUAGE plpgsql
    AS $$
    BEGIN
        LOOP
            -- first try to update
            EXECUTE sql_update;
            -- check if the row is found
            IF FOUND THEN
                RETURN;
            END IF;
            -- not found so insert the row
            BEGIN
                EXECUTE sql_insert;
                RETURN;
                EXCEPTION WHEN unique_violation THEN
                    -- do nothing and loop
            END;
        END LOOP;
    END;
    $$;
    
    CREATE RULE replace_dns AS 
        ON INSERT TO dns 
        WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
                AND (dns.customer_id = new.customer_id)))) 
        DO INSTEAD UPDATE dns 
            SET hits = new.hits 
            WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));
    
    最新消息:这具有强大的
    CREATE TABLE dns (
        "time" timestamp without time zone NOT NULL,
        customer_id integer NOT NULL,
        hits integer
    );
    
    CREATE RULE replace_dns AS 
        ON INSERT TO dns 
        WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
                AND (dns.customer_id = new.customer_id)))) 
        DO INSTEAD UPDATE dns 
            SET hits = new.hits 
            WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));
    
    CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
      RETURNS boolean AS
    $BODY$
    BEGIN
        UPDATE users SET name = _name WHERE id = _id;
        IF FOUND THEN
            RETURN true;
        END IF;
        BEGIN
            INSERT INTO users (id, name) VALUES (_id, _name);
        EXCEPTION WHEN OTHERS THEN
                UPDATE users SET name = _name WHERE id = _id;
            END;
        RETURN TRUE;
    END;
    
    $BODY$
      LANGUAGE plpgsql VOLATILE STRICT
    
    WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
    INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)
    
    CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
      RETURNS void AS
    $BODY$
    BEGIN
        IF EXISTS(SELECT a FROM tabla WHERE a = key)
            THEN
                UPDATE tabla SET b = data WHERE a = key;
            RETURN;
        ELSE
            INSERT INTO tabla(a,b) VALUES (key, data);
            RETURN;
        END IF;
    END;
    $BODY$
    LANGUAGE plpgsql
    
    CREATE TABLE tablename (a integer primary key, b integer, c integer);
    INSERT INTO tablename (a, b, c) values (1, 2, 3);
    
    INSERT INTO tablename (a,b,c) VALUES (1,2,3)
      ON DUPLICATE KEY UPDATE c=c+1;
    
    INSERT INTO tablename (a, b, c) values (1, 2, 10)
    ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;