Questa è una decisione di implementazione. È descritto nella documentazione di Postgres, WITH
Query (espressioni comuni di tabelle) . Esistono due paragrafi relativi al problema.
Innanzitutto, la ragione del comportamento osservato:
Le istruzioni secondarie in WITH
vengono eseguite simultaneamente tra loro e con la query principale . Pertanto, quando si utilizzano le istruzioni di modifica dei dati in WITH
, l'ordine in cui si verificano effettivamente gli aggiornamenti specificati è imprevedibile. Tutte le istruzioni vengono eseguite con la stessa istantanea (vedere il capitolo 13), quindi non possono "vedere" gli effetti reciproci sulle tabelle di destinazione. Ciò allevia gli effetti dell'imprevedibilità dell'ordine effettivo degli aggiornamenti delle righe e indica che i RETURNING
dati sono l'unico modo per comunicare i cambiamenti tra le diverse WITH
dichiarazioni secondarie e la query principale. Un esempio di questo è che in ...
Dopo aver pubblicato un suggerimento insieme a pgsql-docs , Marko Tiikkaja ha spiegato (che concorda con la risposta di Erwin):
I casi insert-update e insert-delete non funzionano perché UPDATE e DELETE non hanno modo di vedere le righe INSERTed a causa della loro istantanea che è stata eseguita prima che INSERT avvenisse. Non c'è nulla di imprevedibile in questi due casi.
Quindi il motivo per cui la tua dichiarazione non si aggiorna può essere spiegato dal primo paragrafo sopra (sulle "istantanee"). Ciò che accade quando si modificano i CTE è che tutti e la query principale vengono eseguiti e "vedono" la stessa istantanea dei dati (tabelle), come erano immediatamente prima dell'esecuzione dell'istruzione. I CTE possono trasmettere informazioni su ciò che hanno inserito / aggiornato / cancellato tra loro e alla query principale utilizzando la RETURNING
clausola ma non possono vedere direttamente le modifiche nelle tabelle. Quindi vediamo cosa succede nella tua dichiarazione:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
Abbiamo 2 parti, il CTE ( newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
e la query principale:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
Il flusso di esecuzione è qualcosa del genere:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
Di conseguenza, quando la query principale unisce tbl
(come mostrato nell'istantanea) alla newval
tabella, unisce una tabella vuota con una tabella a 1 riga. Ovviamente aggiorna 0 righe. Quindi l'istruzione non è mai arrivata per modificare la riga appena inserita ed è quello che vedi.
La soluzione nel tuo caso è riscrivere l'istruzione per inserire i valori corretti in primo luogo o utilizzare 2 istruzioni. Uno che inserisce e un secondo da aggiornare.
Esistono altre situazioni simili, come se l'istruzione avesse una INSERT
e poi una DELETE
sulle stesse righe. La cancellazione fallirebbe esattamente per gli stessi motivi.
Alcuni altri casi, con update-update e update-delete e il loro comportamento sono spiegati in un paragrafo seguente, nella stessa pagina dei documenti.
Il tentativo di aggiornare la stessa riga due volte in una singola istruzione non è supportato. Viene eseguita solo una delle modifiche, ma non è facile (e talvolta non è possibile) prevedere in modo affidabile quale. Questo vale anche per l'eliminazione di una riga che era già stata aggiornata nella stessa istruzione: viene eseguito solo l'aggiornamento. Pertanto, dovresti generalmente evitare di provare a modificare una singola riga due volte in una singola istruzione. In particolare, evitare di scrivere dichiarazioni secondarie WITH che potrebbero influire sulle stesse righe modificate dall'istruzione principale o da una istruzione secondaria di pari livello. Gli effetti di tale affermazione non saranno prevedibili.
E nella risposta di Marko Tiikkaja:
I casi update-update e update-delete non sono esplicitamente causati dallo stesso dettaglio dell'implementazione sottostante (come i casi insert-update e insert-delete).
Il caso update-update non funziona perché assomiglia internamente al problema di Halloween e Postgres non ha modo di sapere quali tuple andrebbero bene aggiornare due volte e quali potrebbero reintrodurre il problema di Halloween.
Quindi la ragione è la stessa (come vengono implementati i CTE di modifica e come ogni CTE vede la stessa istantanea) ma i dettagli differiscono in questi 2 casi, in quanto più complessi e i risultati possono essere imprevedibili nel caso aggiornamento-aggiornamento.
Nell'insert-update (come nel tuo caso) e in un simile insert-delete i risultati sono prevedibili. Solo l'inserimento avviene poiché la seconda operazione (aggiorna o elimina) non ha modo di vedere e influire sulle righe appena inserite.
La soluzione suggerita è la stessa per tutti i casi che tentano di modificare le stesse righe più di una volta: non farlo. Scrivi istruzioni che modificano ogni riga una volta o usa istruzioni separate (2 o più).