É o problema recorrente de
SELECT or INSERT
, relacionado a (mas diferente de) um UPSERT. A nova funcionalidade UPSERT no Postgres 9.5 ainda é fundamental. WITH ins AS (
INSERT INTO names(name)
VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = NULL
WHERE FALSE -- never executed, but locks the row
RETURNING id
)
SELECT id FROM ins
UNION ALL
SELECT id FROM names
WHERE name = 'bob' -- only executed if no INSERT
LIMIT 1;
Dessa forma, você não escreve uma nova versão de linha sem necessidade.
No entanto , ainda há um pequeno caso de curva para uma condição de corrida . As transações simultâneas podem ter adicionado uma linha conflitante, que ainda não está visível na mesma instrução. Então
INSERT
e SELECT
venha vazio. Solução adequada para UPSERT de linha única:
- O SELECT ou INSERT está em uma função propensa a condições de corrida?
Soluções gerais para UPSERT a granel:
- Como usar RETURNING com ON CONFLICT no PostgreSQL?
Sem carga de gravação simultânea
Se gravações simultâneas (de uma sessão diferente) não forem possíveis, você não precisará bloquear a linha e poderá simplificar:
WITH ins AS (
INSERT INTO names(name)
VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO NOTHING -- no lock needed
RETURNING id
)
SELECT id FROM ins
UNION ALL
SELECT id FROM names
WHERE name = 'bob' -- only executed if no INSERT
LIMIT 1;