これは実装の決定です。これはPostgresのドキュメント、WITH
クエリ(共通テーブル式)で説明されています。この問題に関連する2つの段落があります。
まず、観察された動作の理由:
のサブステートメントはWITH
、互いに、およびメインクエリと同時に実行されます。したがって、でデータ変更ステートメントを使用する場合WITH
、指定された更新が実際に行われる順序は予測できません。すべてのステートメントは同じスナップショット(第13章を参照)で実行されるため、ターゲットテーブルに対する互いの影響を「確認」することはできません。これは緩和行の更新の実際の順序の予測不可能性の影響を、その手段RETURNING
のデータが異なるの間で変化して通信するための唯一の方法であるWITH
サブステートメントとメインクエリを。この例は...
pgsql-docsに沿って提案を投稿した後、Marko Tiikkajaが説明しました(これはErwinの回答に同意します)。
INSERTが発生する前にスナップショットが取得されているため、UPDATEとDELETEはINSERTされた行を確認する方法がないため、挿入と更新および挿入と削除のケースは機能しません。これら2つのケースについては、予測できないことは何もありません。
したがって、ステートメントが更新されない理由は、上記の最初の段落(「スナップショット」について)で説明できます。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;
2つの部分、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つのステートメントを使用することです。1つは挿入し、もう1つは更新します。
ステートメントに同じ行にanがINSERT
あり、次にa がある場合など、他にも同様の状況がありDELETE
ます。削除もまったく同じ理由で失敗します。
update-updateとupdate-deleteを使用する他のいくつかのケースとその動作は、同じドキュメントページの次の段落で説明されています。
1つのステートメントで同じ行を2回更新することはサポートされていません。変更は1つしか行われませんが、どの変更を確実に予測することは簡単ではありません(場合によっては不可能です)。これは、同じステートメントですでに更新された行の削除にも適用されます。更新のみが実行されます。したがって、通常、1つのステートメントで1つの行を2回変更することは避けてください。特に、メインステートメントまたは兄弟サブステートメントによって変更された同じ行に影響を与える可能性のあるWITHサブステートメントの記述は避けてください。そのような声明の影響は予測できません。
そして、Marko Tiikkajaからの返信で:
update-updateとupdate-deleteのケースは、(insert-updateとinsert-deleteのケースのように)同じ基礎となる実装の詳細によって明示的に引き起こされるわけではありません。
update-updateケースは、内部的にはハロウィーンの問題のように見えるため機能しません。Postgresは、どのタプルが2回更新しても問題ないか、どのタプルがハロウィーンの問題を再導入できるかを知る方法がありません。
したがって、理由は同じですが(CTEの変更方法と各CTEが同じスナップショットを表示する方法)、詳細はこれらの2つのケースでは異なります。複雑なため、更新と更新のケースでは結果が予測できないためです。
insert-update(あなたの場合)と同様のinsert-deleteの結果は予測可能です。2番目の操作(更新または削除)には、新しく挿入された行を表示して影響を与える方法がないため、挿入のみが行われます。
ただし、推奨される解決策は、同じ行を複数回変更しようとするすべてのケースで同じです:しないでください。各行を1回変更するステートメントを記述するか、個別の(2つ以上の)ステートメントを使用します。