postgres:更新冲突插入行并返回旧值

Posted

技术标签:

【中文标题】postgres:更新冲突插入行并返回旧值【英文标题】:postgres: update a row on conflict insert and return old values 【发布时间】:2019-08-07 12:51:52 【问题描述】:

我需要一个查询来更新表中的一行,但如果 id 不存在,它会插入默认值。它还必须避免线程竞争条件。

我在这里找到了一个应该没问题的答案 https://***.com/a/7927957/8372336

使用这个查询:

UPDATE tbl x
SET    tbl_id = 24
     , name = 'New Gal'
FROM  (SELECT tbl_id, name FROM tbl WHERE tbl_id = 4 FOR UPDATE) y 
WHERE  x.tbl_id = y.tbl_id
RETURNING y.tbl_id AS old_id, y.name AS old_name, x.tbl_id, x.name;

所以我认为它应该在更新后返回旧值,并且应该防止线程竞争条件。

但是如果该行不存在,我需要添加一个插入,并且这次还返回插入的值(旧值没有意义,因为它们不存在)。

所以基本上我需要做类似的事情

INSERT INTO tbl 
    (...) VALUES (...) 
    RETURNING ..., ... 
ON CONFLICT DO
UPDATE tbl x
SET    tbl_id = 24
     , name = 'New Gal'
FROM  (SELECT tbl_id, name FROM tbl WHERE tbl_id = 4 FOR UPDATE) y 
WHERE  x.tbl_id = y.tbl_id
RETURNING y.tbl_id AS old_id, y.name AS old_name, x.tbl_id, x.name;

但我不确定这样的事情是否可行。我怎样才能让它工作并确保竞争条件?

【问题讨论】:

这可能需要一个触发器。 @GordonLinoff 你的意思是在我运行我发布的第一个更新 sn-p 之前执行插入?所以在触发器中我运行插入冲突什么都不做,然后第一次更新sn-p??它会确保比赛条件吗? 。 .触发器可能不是必需的。请参阅下面的答案。 【参考方案1】:

不知何故,这似乎有效:

insert into t (x)
    values ('a0'), ('b')
    on conflict (x) do update
        set x = excluded.x || '0'
    returning i, x, (select x from t t2 where t2.i = t.i);

我很惊讶,因为t 在子查询的范围内,但excluded 不在。嗯。 . .也许那是因为它不是on conflict 子句的一部分,而是整个insert 的一部分。这开始有意义了。

Here 是这个版本的 dbfiddle。

我认为您的代码如下所示:

INSERT INTO tbl (...)
    VALUES (...) 
ON CONFLICT DO
UPDATE tbl x
    SET tbl_id = 24,
        name = 'New Gal'
RETURNING (SELECT t2.tbl_id FROM tbl t2 WHERE t2.tbl_id = tbl.tbl_id) AS old_id, 
          (SELECT t2.name FROM tbl t2 WHERE t2.tbl_id = tbl.tbl_id) AS old_name, 
          x.tbl_id, x.name;

【讨论】:

谢谢,但它会在更新时返回旧值,而在插入时返回新值? @91DarioDev 。 . .在 dbfiddle 中,它为 insertupdate 返回新值列中的新值和旧值列中的旧值。 抱歉,但我需要在更新之前返回所有列的值以防更新和当前添加的值以防运行插入。例如,如果 id 为 24 的行已经存在并且名称为 'foo' 并且我使用 'bar' 更新名称,如果 id 为 24 的行不存在,则返回 'foo' 我添加 id=24 和 name='created_now'它返回 created_now @91DarioDev 。 . .你可以设置一个 dbfiddle 或者用我设置的小提琴来描述这个吗?根据您的描述,这正是查询所做的。子查询返回旧值,新值也在returning 子句中。 它对我有用,我只是想检查这个操作是插入还是更新,然后在应用程序中使用它来做我需要的事情。在我的情况下,我返回“旧更新列”,如果它为空,那是因为它是现在创建的。

以上是关于postgres:更新冲突插入行并返回旧值的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data / Hibernate 使用 Postgres 保存实体,在冲突更新某些字段时使用插入

房间更新(如果不存在则插入)行并返回计数更改的行

如何合并表上的行并更新 postgres 上的联结表

使用 Apache Airflow 更新和维护 postgres 表

创建一个触发器,在更新行时将旧值插入其他表

如何使用 node-postgres 进行批量插入