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

91D*_*Dev 4 sql postgresql insert-update race-condition select-for-update

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

我在这里找到了一个应该没问题的答案 /sf/answers/554957021/

使用此查询:

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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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;
Run Code Online (Sandbox Code Playgroud)

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

Gor*_*off 6

不知何故,这似乎有效:

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);
Run Code Online (Sandbox Code Playgroud)

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

是此版本的 db<>fiddle。

我认为你的代码看起来像:

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;
Run Code Online (Sandbox Code Playgroud)