Esta é uma decisão de implementação. É descrito na documentação do Postgres, WITH
Consultas (expressões comuns de tabela) . Existem dois parágrafos relacionados ao problema.
Primeiro, a razão do comportamento observado:
As sub-instruções em WITH
são executadas simultaneamente entre si e com a consulta principal . Portanto, ao usar instruções de modificação de dados WITH
, a ordem na qual as atualizações especificadas realmente acontecem é imprevisível. Todas as instruções são executadas com o mesmo instantâneo (consulte o Capítulo 13), portanto, elas não podem "ver" os efeitos umas das outras nas tabelas de destino. Isso alivia os efeitos da imprevisibilidade da ordem real das atualizações de linha e significa que os RETURNING
dados são a única maneira de comunicar alterações entre diferentes WITH
sub-instruções e a consulta principal. Um exemplo disso é que em ...
Depois de postar uma sugestão junto ao pgsql-docs , Marko Tiikkaja explicou (o que concorda com a resposta de Erwin):
Os casos insert-update e insert-delete não funcionam porque os UPDATEs e DELETEs não têm como ver as linhas INSERTed devido ao fato de sua captura instantânea ter sido tirada antes que o INSERT acontecesse. Não há nada imprevisível nesses dois casos.
Portanto, a razão pela qual sua declaração não é atualizada pode ser explicada pelo primeiro parágrafo acima (sobre "instantâneos"). O que acontece quando você modifica CTEs é que todos eles e a consulta principal são executados e "veem" o mesmo instantâneo dos dados (tabelas), como eram imediatamente antes da execução da instrução. Os CTEs podem passar informações sobre o que foram inseridos / atualizados / excluídos entre si e para a consulta principal usando a RETURNING
cláusula, mas eles não podem ver as alterações nas tabelas diretamente. Então, vamos ver o que acontece em sua declaração:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
Temos 2 partes, a CTE ( newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
e a consulta principal:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
O fluxo de execução é algo como isto:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
Como resultado, quando a consulta principal une o tbl
(como visto no instantâneo) à newval
tabela, ela une uma tabela vazia a uma tabela de 1 linha. Obviamente, ele atualiza 0 linhas. Portanto, a declaração nunca chegou a modificar a linha recém-inserida e é isso que você vê.
A solução no seu caso é reescrever a instrução para inserir os valores corretos em primeiro lugar ou usar 2 instruções. Um que insere e um segundo para atualizar.
Existem outras situações semelhantes, como se a instrução tivesse um INSERT
e depois um DELETE
nas mesmas linhas. A exclusão falharia exatamente pelos mesmos motivos.
Alguns outros casos, com atualização-atualização e atualização-exclusão e seu comportamento, são explicados no parágrafo seguinte, na mesma página de documentos.
Tentar atualizar a mesma linha duas vezes em uma única instrução não é suportado. Apenas uma das modificações ocorre, mas não é fácil (e às vezes não é possível) prever com precisão qual. Isso também se aplica à exclusão de uma linha que já foi atualizada na mesma instrução: somente a atualização é executada. Portanto, você geralmente deve evitar tentar modificar uma única linha duas vezes em uma única instrução. Em particular, evite escrever sub-instruções WITH que possam afetar as mesmas linhas alteradas pela instrução principal ou por uma sub-instrução irmã. Os efeitos dessa declaração não serão previsíveis.
E na resposta de Marko Tiikkaja:
Os casos update-update e update-delete não são explicitamente causados pelos mesmos detalhes de implementação subjacentes (como os casos insert-update e insert-delete).
O caso atualização-atualização não funciona porque parece internamente com o problema do Dia das Bruxas, e o Postgres não tem como saber quais tuplas poderiam atualizar duas vezes e quais poderiam reintroduzir o problema do Dia das Bruxas.
Portanto, o motivo é o mesmo (como as CTEs modificadas são implementadas e como cada CTE vê o mesmo instantâneo), mas os detalhes diferem nesses 2 casos, pois são mais complexos e os resultados podem ser imprevisíveis no caso de atualização e atualização.
Na inserção-atualização (como o seu caso) e em uma inserção-exclusão semelhante, os resultados são previsíveis. Somente a inserção acontece porque a segunda operação (atualização ou exclusão) não tem como ver e afetar as linhas recém-inseridas.
A solução sugerida é a mesma para todos os casos que tentam modificar as mesmas linhas mais de uma vez: Não faça isso. Escreva instruções que modifiquem cada linha uma vez ou use instruções separadas (2 ou mais).