Il s'agit d'une décision de mise en œuvre. Il est décrit dans la documentation Postgres, WITH
Requêtes (expressions de table communes) . Il y a deux paragraphes liés à la question.
Tout d'abord, la raison du comportement observé:
Les sous-instructions de WITH
sont exécutées simultanément entre elles et avec la requête principale . Par conséquent, lorsque vous utilisez des instructions de modification de données dans WITH
, l'ordre dans lequel les mises à jour spécifiées se produisent réellement est imprévisible. Toutes les instructions sont exécutées avec le même instantané (voir le chapitre 13), elles ne peuvent donc pas "voir" les effets des autres sur les tables cibles. Cela atténue les effets de l'imprévisibilité de l'ordre réel des mises à jour des lignes et signifie que les RETURNING
données sont le seul moyen de communiquer les modifications entre les différentes WITH
sous-instructions et la requête principale. Un exemple de ceci est qu'en ...
Après avoir posté une suggestion sur pgsql-docs , Marko Tiikkaja a expliqué (ce qui correspond à la réponse d'Erwin):
Les cas d'insertion-mise à jour et d'insertion-suppression ne fonctionnent pas car les mises à jour et les suppressions n'ont aucun moyen de voir les lignes insérées car leur instantané a été pris avant que l'insertion ne se produise. Il n'y a rien d'imprévisible dans ces deux cas.
Ainsi, la raison pour laquelle votre déclaration ne se met pas à jour peut être expliquée par le premier paragraphe ci-dessus (à propos des "instantanés"). Ce qui se passe lorsque vous avez modifié des CTE, c'est que tous et la requête principale sont exécutés et "voient" le même instantané des données (tables), comme c'était immédiatement avant l'exécution de l'instruction. Les CTE peuvent se transmettre des informations sur ce qu'ils ont inséré / mis à jour / supprimé entre eux et à la requête principale en utilisant la RETURNING
clause, mais ils ne peuvent pas voir directement les modifications dans les tableaux. Voyons donc ce qui se passe dans votre déclaration:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
Nous avons 2 parties, le CTE ( newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
et la requête principale:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
Le flux d'exécution est quelque chose comme ceci:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
Par conséquent, lorsque la requête principale rejoint le tbl
(comme le montre l'instantané) avec la newval
table, elle joint une table vide avec une table à 1 ligne. De toute évidence, il met à jour 0 lignes. Donc, l'instruction n'est jamais vraiment venue modifier la ligne nouvellement insérée et c'est ce que vous voyez.
Dans votre cas, la solution consiste à réécrire l'instruction pour insérer les valeurs correctes en premier lieu ou à utiliser 2 instructions. Un qui insère et un second à mettre à jour.
Il existe d'autres situations similaires, comme si l'instruction avait un INSERT
puis un DELETE
sur les mêmes lignes. La suppression échouerait exactement pour les mêmes raisons.
Certains autres cas, avec update-update et update-delete et leur comportement sont expliqués dans un paragraphe suivant, dans la même page de documentation.
Essayer de mettre à jour la même ligne deux fois dans une seule instruction n'est pas pris en charge. Une seule des modifications a lieu, mais il n'est pas facile (et parfois impossible) de prédire de manière fiable laquelle. Cela s'applique également à la suppression d'une ligne déjà mise à jour dans la même instruction: seule la mise à jour est effectuée. Par conséquent, vous devez généralement éviter d'essayer de modifier une seule ligne deux fois dans une seule instruction. En particulier, évitez d'écrire des sous-instructions WITH qui pourraient affecter les mêmes lignes modifiées par l'instruction principale ou une sous-instruction frère. Les effets d'une telle déclaration ne seront pas prévisibles.
Et dans la réponse de Marko Tiikkaja:
Les cas de mise à jour-mise à jour et de suppression-mise à jour ne sont explicitement pas causés par les mêmes détails d'implémentation sous-jacents (comme les cas d'insertion-mise à jour et d'insertion-suppression).
Le cas de mise à jour-mise à jour ne fonctionne pas car il ressemble en interne au problème d'Halloween, et Postgres n'a aucun moyen de savoir quels tuples seraient corrects de mettre à jour deux fois et lesquels pourraient réintroduire le problème d'Halloween.
La raison est donc la même (comment les CTE de modification sont implémentés et comment chaque CTE voit le même instantané) mais les détails diffèrent dans ces 2 cas, car ils sont plus complexes et les résultats peuvent être imprévisibles dans le cas de mise à jour-mise à jour.
Dans l'insertion-mise à jour (selon votre cas) et une insertion-suppression similaire, les résultats sont prévisibles. Seule l'insertion se produit car la deuxième opération (mise à jour ou suppression) n'a aucun moyen de voir et d'affecter les lignes nouvellement insérées.
La solution suggérée est cependant la même pour tous les cas qui essaient de modifier les mêmes lignes plus d'une fois: ne le faites pas. Écrivez des instructions qui modifient chaque ligne une fois ou utilisez des instructions distinctes (2 ou plus).