在PostgreSQL中插入重复更新?

几个月前,我从Stack Overflow的一个答案中学习了如何在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文档中的涵盖范围。

为了澄清,我想插入几件事情,如果它们已经存在以更新它们。


自9.5版以来,PostgreSQL具有UPSERT语法,并带有ON CONFLICT子句。 使用以下语法(类似于MySQL)

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

在手册中搜索postgresql的“upsert”电子邮件组档案可以找到你想要做的事情的例子:

例38-2。 UPDATE / INSERT异常

本示例使用异常处理来酌情执行UPDATE或INSERT:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        -- note that "a" must be unique
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

在黑客邮件列表中,可能有一个如何在9.1和更高版本中使用CTE批量执行此操作的示例:

WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;

请参阅a_horse_with_no_name的答案以获得更清晰的示例。


警告:如果同时从多个会话执行,这是不安全的 (参见下面的注意事项)。


在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将成功,否则它不起作用。

只有当“id = 3”的行不存在时,INSERT才会成功。

您可以将这两个字符串合并为一个字符串,并使用从您的应用程序执行的单个SQL语句来运行它们。 强烈建议在一次交易中将它们一起运行。

在隔离或锁定表上运行时,这种方式运行得非常好,但受到竞争条件的影响,这意味着如果同时插入一行,它可能仍会因重复键错误而失败,或者在同时删除一行时可能终止, 。 对于PostgreSQL 9.1或更高版本的SERIALIZABLE事务将以非常高的序列化失败率为代价进行可靠处理,这意味着您必须重试很多事务。 看看为什么upsert如此复杂,这将更详细地讨论这种情况。

除非应用程序检查受影响的行计数并验证insertupdate影响到一行,否则此方法还会丢失read committed隔离中的update


使用PostgreSQL 9.1,可以使用可写的CTE(公用表表达式)来实现:

WITH new_values (id, field1, field2) as (
  values 
     (1, 'A', 'X'),
     (2, 'B', 'Y'),
     (3, 'C', 'Z')

),
upsert as
( 
    update mytable m 
        set field1 = nv.field1,
            field2 = nv.field2
    FROM new_values nv
    WHERE m.id = nv.id
    RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1 
                  FROM upsert up 
                  WHERE up.id = new_values.id)

查看这些博客条目:

  • 通过可写CTE插入
  • 等待9.1 - 可写的CTE
  • 为什么这么复杂?

  • 请注意,这种解决方案并不妨碍惟一键冲突,但它是不容易丢失更新。
    请参阅dba.stackexchange.com上Craig Ringer的后续信息

    链接地址: http://www.djcxy.com/p/19789.html

    上一篇: Insert, on duplicate update in PostgreSQL?

    下一篇: ON DUPLICATE KEY UPDATE