Esta es una decisión de implementación. Se describe en la documentación de Postgres, WITH
Consultas (expresiones de tabla comunes) . Hay dos párrafos relacionados con el tema.
Primero, la razón del comportamiento observado:
Las subdeclaraciones WITH
se ejecutan simultáneamente entre sí y con la consulta principal . Por lo tanto, cuando se utilizan declaraciones de modificación de datos WITH
, el orden en el que las actualizaciones especificadas suceden realmente es impredecible. Todas las declaraciones se ejecutan con la misma instantánea (ver Capítulo 13), por lo que no pueden "ver" los efectos de los demás en las tablas de destino. Esto alivia los efectos de la imprevisibilidad del orden real de las actualizaciones de filas y significa que los RETURNING
datos son la única forma de comunicar los cambios entre las diferentes WITH
subdeclaraciones y la consulta principal. Un ejemplo de esto es que en ...
Después de publicar una sugerencia junto con pgsql-docs , Marko Tiikkaja explicó (lo que concuerda con la respuesta de Erwin):
Los casos de inserción-actualización e inserción-eliminación no funcionan porque las ACTUALIZACIONES y DELETES no tienen forma de ver las filas INSERTED debido a que se tomó su instantánea antes de que ocurriera el INSERT. No hay nada impredecible en estos dos casos.
Entonces, la razón por la cual su declaración no se actualiza puede explicarse en el primer párrafo anterior (sobre "instantáneas"). Lo que sucede cuando se modifican los CTE es que todos ellos y la consulta principal se ejecutan y "ven" la misma instantánea de los datos (tablas), tal como estaban inmediatamente antes de la ejecución de la instrucción. Los CTE pueden pasar información sobre lo que insertaron / actualizaron / eliminaron entre sí y a la consulta principal utilizando la RETURNING
cláusula, pero no pueden ver los cambios en las tablas directamente. Entonces, veamos qué sucede en su declaración:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
Tenemos 2 partes, el CTE ( newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
y la consulta principal:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
El flujo de ejecución es algo como esto:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
Como resultado, cuando la consulta principal une la tbl
(como se ve en la instantánea) con la newval
tabla, une una tabla vacía con una tabla de 1 fila. Obviamente actualiza 0 filas. Entonces, la declaración nunca llegó a modificar la fila recién insertada y eso es lo que ves.
La solución en su caso es reescribir la declaración para insertar los valores correctos en primer lugar o usar 2 declaraciones. Uno que se inserta y un segundo para actualizar.
Hay otras situaciones similares, como si la declaración tuviera un INSERT
y luego un DELETE
en las mismas filas. La eliminación fallaría exactamente por las mismas razones.
Algunos otros casos, con actualización-actualización y actualización-eliminación y su comportamiento se explican en el siguiente párrafo, en la misma página de documentos.
Intentar actualizar la misma fila dos veces en una sola declaración no es compatible. Solo se realiza una de las modificaciones, pero no es fácil (y a veces no es posible) predecir de manera confiable cuál. Esto también se aplica a la eliminación de una fila que ya se actualizó en la misma instrucción: solo se realiza la actualización. Por lo tanto, generalmente debe evitar intentar modificar una sola fila dos veces en una sola instrucción. En particular, evite escribir subdeclaraciones WITH que puedan afectar las mismas filas cambiadas por la declaración principal o una subdeclaración hermana. Los efectos de tal declaración no serán predecibles.
Y en la respuesta de Marko Tiikkaja:
Los casos de actualización-actualización y actualización-eliminación no están explícitamente causados por el mismo detalle de implementación subyacente (como los casos de inserción-actualización y inserción-eliminación).
El caso de actualización-actualización no funciona porque internamente se parece al problema de Halloween, y Postgres no tiene forma de saber qué tuplas estaría bien actualizar dos veces y cuáles podrían reintroducir el problema de Halloween.
Entonces, la razón es la misma (cómo se implementan los CTE modificadores y cómo cada CTE ve la misma instantánea), pero los detalles difieren en estos 2 casos, ya que son más complejos y los resultados pueden ser impredecibles en el caso de actualización-actualización.
En la inserción-actualización (como su caso) y una inserción-eliminación similar, los resultados son predecibles. Solo la inserción ocurre ya que la segunda operación (actualizar o eliminar) no tiene forma de ver y afectar las filas recién insertadas.
Sin embargo, la solución sugerida es la misma para todos los casos que intentan modificar las mismas filas más de una vez: no lo haga. Escriba declaraciones que modifiquen cada fila una vez o use declaraciones separadas (2 o más).