如何用INSERT更新所有列... ON CONFLICT ...?

bea*_*ind 9 sql database postgresql insert upsert

我有一个包含单个主键的表.当我尝试插入时,尝试插入具有现有密钥的行可能会导致冲突.我想允许插入更新所有列?这有什么简单的语法吗?我试图让它"upsert"所有列.

我正在使用PostgreSQL 9.5.5.

Erw*_*ter 13

UPDATE语法 需要明确命名目标列.避免这种情况的可能原因:

  • 你有很多列,只想缩短语法.
  • 除了唯一列之外,您不知道列名.

"All columns"必须表示匹配顺序和匹配数据类型的"目标表的所有列"(或至少"表的前导列").否则,您必须提供目标列名称列表.

测试表:

CREATE TABLE tbl (
   id    int PRIMARY KEY
 , text  text
 , extra text
);

INSERT INTO tbl AS t
VALUES (1, 'foo')
     , (2, 'bar');
Run Code Online (Sandbox Code Playgroud)

1. DELETE&INSERT在单个查询,而不是

不知道除了之外的任何列名id.

仅适用于"目标表的所有列".虽然语法甚至适用于前导子集,但目标表中的多余列将使用DELETE和重置为NULL INSERT.

INSERT ... ON CONFLICT ...需要UPSERT()来避免并发写入加载下的并发/锁定问题,并且只是因为没有通用方法来锁定Postgres中尚未存在的行(值锁定).

您的特殊要求仅影响UPDATE零件.在现有行受到影响的情况下,可能会出现并发症.那些被正确锁定.简化一些,您可以将您的情况下减少DELETEINSERT:

WITH data(id) AS (              -- Only 1st column gets explicit name!
   VALUES
      (1, 'foo_upd', 'a')       -- changed
    , (2, 'bar', 'b')           -- unchanged
    , (3, 'baz', 'c')           -- new
   )
, del AS (
   DELETE FROM tbl AS t
   USING  data d
   WHERE  t.id = d.id
   -- AND    t <> d              -- optional, to avoid empty updates
   )                             -- only works for complete rows
INSERT INTO tbl AS t
TABLE  data                      -- short for: SELECT * FROM data
ON     CONFLICT (id) DO NOTHING
RETURNING t.id;
Run Code Online (Sandbox Code Playgroud)

在Postgres的MVCC模型,一个UPDATE是大致相同DELETE,并INSERT反正(除了某些极端情况并发,HOT更新和存储脱节的大列值).由于您仍想要替换所有行,只需删除之前的冲突行INSERT.在提交事务之前,已删除的行将保持锁定状态.将INSERT可能只对以前不存在的键值找到冲突行,如果并发事务发生并发插入(在后DELETE,但在此之前的INSERT).

在这种特殊情况下,您将丢失受影响行的其他列值.没有例外.但是如果竞争查询具有相同的优先级,那么这几乎不是问题:另一个查询赢得了某些行.此外,如果另一个查询是类似的UPSERT,它的替代方法是等待此事务提交然后立即更新."获胜"可能是一场惨淡的胜利.

关于"空更新":

不,我的查询必须赢!

好的,你要求它:

WITH data(id) AS (                   -- Only 1st column gets explicit name!
   VALUES                            -- rest gets default names "column2", etc.
   (1, 'foo_upd', NULL)              -- changed
 , (2, 'bar', NULL)                  -- unchanged
 , (3, 'baz', NULL)                  -- new
 , (4, 'baz', NULL)                  -- new
   )
 , ups AS (
   INSERT INTO tbl AS t
   TABLE  data                       -- short for: SELECT * FROM data
   ON     CONFLICT (id) DO UPDATE
   SET    id = t.id
   WHERE  false                      -- never executed, but locks the row!
   RETURNING t.id
   )
 , del AS (
   DELETE FROM tbl AS t
   USING  data     d
   LEFT   JOIN ups u USING (id)
   WHERE  u.id IS NULL               -- not inserted !
   AND    t.id = d.id
   -- AND    t <> d                  -- avoid empty updates - only for full rows
   RETURNING t.id
   )
 , ins AS (
   INSERT INTO tbl AS t
   SELECT *
   FROM   data
   JOIN   del USING (id)             -- conflict impossible!
   RETURNING id
   )
SELECT ARRAY(TABLE ups) AS inserted  -- with UPSERT
     , ARRAY(TABLE ins) AS updated   -- with DELETE & INSERT;
Run Code Online (Sandbox Code Playgroud)

怎么样?

  • 第一个CTE data只提供数据.可能是一张桌子.
  • 第二届CTE ups:UPSERT.冲突的行id不会更改,但也会被锁定.
  • 第三个CTE del删除冲突的行.他们仍然被锁定.
  • 第4个CTE ins插入整行.仅允许进行相同的交易
  • 最终的SELECT仅用于演示以显示发生的事情.

要检查空更新测试(之前和之后):

SELECT ctid, * FROM tbl; -- did the ctid change?
Run Code Online (Sandbox Code Playgroud)

2.动态SQL

这也适用于前导列的子集,保留现有值.

诀窍是让Postgres动态地用系统目录中的列名构建查询字符串,然后执行它.

查看代码的相关答案:

  • UPDATE 和 DELETE+INSERT 之间的一个非常重要的区别是,它会导致触发不同的触发器,如果​​触发器对您很重要,那么这就是一个问题。 (3认同)