I need a query to update a row in table, but if the id doesn't exist it inserts default values. Also it has to avoid threads race conditions.
I found an answer here that should be fine https://stackoverflow.com/a/7927957/8372336
Using this query:
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;
So I think it should return old values after updating and it should prevent threads race conditions.
But I need to add an insert if the row doesn't exist and also return the inserted values this time (old values doesn't make sense because they didn't exist).
So basically I need to do something like
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;
but i'm not sure if something like this could work. how can i make it work and make it sure of race conditions?
Somehow, this seems to work:
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);
I am surprised because t
is in scope in the subquery, but excluded
is not. Hmmm . . . perhaps that is because it is not part of the on conflict
clause but part of the overall insert
. That starts to make sense.
Here is a db<>fiddle for this version.
I think your code would look like:
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;