这是一个实施决策。它在Postgres文档“ WITH
查询(公用表表达式)”中进行了描述。有两个与该问题有关的段落。
首先,观察到的行为的原因:
中的子语句WITH
彼此并与主查询并发执行。因此,当在中使用数据修改语句时WITH
,指定更新实际发生的顺序是不可预测的。所有语句都使用相同的快照执行(请参见第13章),因此它们无法“看到”彼此对目标表的影响。这减轻了行更新实际顺序的不可预测性的影响,并且意味着RETURNING
数据是在不同WITH
子语句和主查询之间传达更改的唯一途径。一个例子是...
在我向pgsql-docs发布建议之后,Marko Tiikkaja解释了(这与Erwin的回答是一致的):
insert-update和insert-delete情况不起作用,因为UPDATE和DELETE无法看到INSERTed行,因为在INSERT发生之前已拍摄了快照。这两种情况没有什么不可预测的。
因此,您的陈述不更新的原因可以由上面的第一段(关于“快照”)进行解释。修改CTE时发生的情况是,所有CTE和主查询均已执行,并且“看到”相同的数据快照(表),就像它们在执行语句之前一样。CTE可以使用RETURNING
子句将有关它们插入/更新/删除的信息以及彼此之间的信息传递给主查询,但是它们无法直接在表中看到更改。因此,让我们看看您的语句中发生了什么:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
我们分为两部分,CTE(newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
和主要查询:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
执行流程如下所示:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
结果,当主查询与表连接tbl
(如快照所示)时,主查询将newval
空表与1行表连接。显然,它更新了0行。因此,该语句从未真正地来修改新插入的行,这就是您所看到的。
您的解决方案是重写语句以首先插入正确的值,或者使用2条语句。一个插入,第二个更新。
还有其他类似的情况,例如,如果语句在同一行上有一个INSERT
然后有一个DELETE
。删除将由于完全相同的原因而失败。
在同一文档页面的下一段中,将介绍其他一些带有update-update和update-delete及其行为的情况。
不支持在单个语句中尝试两次更新同一行。只有一种修改发生,但要可靠地预测哪一种修改并不容易(有时甚至是不可能)。这也适用于删除同一条语句中已更新的行:仅执行更新。因此,通常应避免在单个语句中尝试两次修改单个行。特别要避免编写WITH子语句,这可能会影响由主语句或同级子语句更改的同一行。这种陈述的影响是不可预测的。
在Marko Tiikkaja的回复中:
显然,update-update和update-delete案例不是由相同的基础实现细节引起的(如insert-update和insert-delete案例)。
update-update案例不起作用,因为它内部看起来像万圣节问题,而Postgres无法知道哪些元组可以更新两次,哪些元组可以重新引入万圣节问题。
因此,原因是相同的(修改CTE的实现方式以及每个CTE如何看到相同的快照),但是这两种情况的细节有所不同,因为它们更加复杂,并且在update-update情况下结果无法预测。
在insert-update(视您的情况)和类似的insert-delete中,结果是可预测的。只有插入发生,因为第二项操作(更新或删除)无法查看和影响新插入的行。
但是,对于尝试多次修改同一行的所有情况,建议的解决方案都是相同的:不要这样做。编写修改每行一次的语句,或使用单独的(2个或更多)语句。