이것은 구현 결정입니다. Postgres 설명서, WITH
쿼리 (공통 테이블 식)에 설명되어 있습니다. 이 문제와 관련된 두 개의 단락이 있습니다.
첫째, 관찰 된 행동의 이유 :
의 하위 문은 WITH
동시에 실행되는 서로 메인 쿼리 . 따라서에서 데이터 수정 문 WITH
을 사용할 때 지정된 업데이트가 실제로 발생하는 순서는 예측할 수 없습니다. 모든 명령문은 동일한 스냅 샷으로 실행되므로 (13 장 참조) 대상 테이블에 대한 서로의 영향을 "볼"수 없습니다. 이는 실제 행 갱신 순서의 예측 불가능의 영향을 완화 시키며, 데이터가 다른 하위 명령문과 기본 쿼리 간의 변경 사항을 전달하는 유일한 방법 임을 의미 합니다. RETURNING
WITH
이것의 예는 ...
pgsql-docs 와 함께 제안을 게시 한 후 Marko Tiikkaja는 다음과 같이 설명했습니다 (Erwin의 답변에 동의합니다).
UPDATE 및 DELETE에는 INSERT가 발생하기 전에 스냅 샷이 작성되어 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
같은 행에 있습니다. 동일한 이유로 삭제가 실패합니다.
업데이트-업데이트 및 업데이트-삭제 및 동작이있는 일부 다른 경우는 동일한 문서 페이지의 다음 단락에서 설명합니다.
단일 명령문에서 동일한 행을 두 번 업데이트하는 것은 지원되지 않습니다. 수정 중 하나만 수행되지만 어느 것을 신뢰할 수 있는지 예측하는 것은 쉽지 않습니다 (때로는 불가능하지도 않습니다). 이는 동일한 명령문에서 이미 업데이트 된 행을 삭제하는 경우에도 적용됩니다. 업데이트 만 수행됩니다. 따라서 일반적으로 단일 명령문에서 단일 행을 두 번 수정하지 마십시오. 특히 기본 명령문 또는 형제 하위 명령문에 의해 변경된 동일한 행에 영향을 줄 수있는 WITH 하위 명령문을 작성하지 마십시오. 그러한 진술의 효과는 예측할 수 없습니다.
그리고 Marko Tiikkaja의 답변에서 :
update-update 및 update-delete 케이스는 동일한 기본 구현 세부 사항 (insert-update 및 insert-delete 케이스) 으로 인해 명시 적으로 발생 하지 않습니다 .
내부적으로 할로윈 문제처럼 보이기 때문에 업데이트 업데이트 사례가 작동하지 않으며 Postgres는 어떤 튜플을 두 번 업데이트해도 좋으며 어느 것이 어느 할로윈 문제를 다시 도입 할 수 있는지 알 방법이 없습니다.
따라서 이유는 동일하지만 (CTE 수정 방법과 각 CTE가 동일한 스냅 샷을 보는 방법) 세부 사항은이 두 경우에서 더 복잡하고 업데이트-업데이트 사례에서 결과를 예측할 수 없기 때문에 서로 다릅니다.
삽입 업데이트 (귀하의 경우) 및 유사한 삽입 삭제에서 결과를 예측할 수 있습니다. 두 번째 작업 (업데이트 또는 삭제)이 새로 삽입 된 행을보고 영향을주는 방법이 없으므로 삽입 만 발생합니다.
그러나 제안 된 솔루션은 동일한 행을 두 번 이상 수정하려고하는 모든 경우에 동일합니다.하지 마십시오. 각 행을 한 번 수정하거나 별도 (2 개 이상) 명령문을 사용하는 명령문을 작성하십시오.