No PostgreSQL 9.5, dada uma tabela simples criada com:
create table tbl (
id serial primary key,
val integer
);
Eu corro o SQL para INSERT um valor e, em seguida, ATUALIZO na mesma instrução:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
O resultado é que UPDATE é ignorado:
testdb=> select * from tbl;
┌────┬─────┐
│ id │ val │
├────┼─────┤
│ 1 │ 1 │
└────┴─────┘
Por que é isso? Essa limitação faz parte do padrão SQL (ou seja, presente em outros bancos de dados) ou é algo específico do PostgreSQL que pode ser corrigido no futuro? A documentação de consultas WITH diz que vários UPDATEs não são suportados, mas não menciona INSERTs e UPDATEs.
Todas as subdeclarações de uma consulta com CTEs acontecem praticamente ao mesmo tempo. Ou seja, eles são baseados no mesmo instantâneo do banco de dados.
O
UPDATE
vê o mesmo estado da tabela subjacente que oINSERT
, o que significa que a linha comval = 1
ainda não está lá. O manual esclarece aqui:Cada instrução pode ver o que é retornado por outra CTE na
RETURNING
cláusula. Mas as tabelas subjacentes parecem todas iguais para eles.Você precisaria de duas instruções (em uma única transação) para o que está tentando fazer. O exemplo dado deve ser apenas um único
INSERT
para começar, mas isso pode ser devido ao exemplo simplificado.Esta é uma decisão de implementação. Ele é descrito na documentação do Postgres,
WITH
Queries (Common Table Expressions) . Há dois parágrafos relacionados ao assunto.Primeiro, o motivo do comportamento observado:
Depois que postei uma sugestão junto com pgsql-docs , Marko Tiikkaja explicou (que concorda com a resposta de Erwin):
Portanto, o motivo pelo qual sua declaração não é atualizada pode ser explicado no 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 "vêem" o mesmo instantâneo dos dados (tabelas), como estavam imediatamente antes da execução da instrução. Os CTEs podem passar informações sobre o que inseriram/atualizaram/excluíram entre si e para a consulta principal usando a
RETURNING
cláusula, mas não podem ver as alterações nas tabelas diretamente. Então vamos ver o que acontece na sua declaração:Temos 2 partes, o CTE (
newval
):e a consulta principal:
O fluxo de execução é mais ou menos assim:
Como resultado, quando a consulta principal junta
tbl
(como visto no instantâneo) com anewval
tabela, ela junta uma tabela vazia com uma tabela de 1 linha. Obviamente ele atualiza 0 linhas. Portanto, a instrução nunca realmente veio para 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 umDELETE
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 documentação.
E na resposta de Marko Tiikkaja:
Portanto, o motivo é o mesmo (como os CTEs modificadores são implementados 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-atualização.
No insert-update (conforme o seu caso) e em um insert-delete semelhante, os resultados são previsíveis. Apenas a inserção acontece, pois a segunda operação (atualizar ou excluir) não tem como ver e afetar as linhas recém-inserida.
A solução sugerida, porém, é 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).