CTE에 삽입 된 행을 동일한 명령문으로 업데이트 할 수없는 이유는 무엇입니까?


13

PostgreSQL 9.5에서 다음과 같이 생성 된 간단한 테이블이 주어졌습니다.

create table tbl (
    id serial primary key,
    val integer
);

SQL을 실행하여 값을 삽입 한 다음 동일한 명령문에서 업데이트합니다.

WITH newval AS (
    INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;

결과적으로 UPDATE가 무시됩니다.

testdb=> select * from tbl;
┌────┬─────┐
 id  val 
├────┼─────┤
  1    1 
└────┴─────┘

왜 이런거야? 이 제한 사항이 SQL 표준 (예 : 다른 데이터베이스에 있음)의 일부입니까, 아니면 나중에 수정 될 PostgreSQL에 특정한 것입니까? WITH 쿼리 문서는 여러 업데이트가 지원되지 않습니다라고하지만, 삽입하고 업데이트를 언급하지 않습니다.

답변:


15

CTE의 모든 진술은 사실상 동시에 발생합니다. 즉, 데이터베이스의 동일한 스냅 샷을 기반으로합니다.

UPDATE은 AS 기본 테이블의 동일한 상태보고 INSERT있는 행을 의미 val = 1가없는, 아직. 매뉴얼은 다음을 설명합니다.

모든 명령문은 동일한 스냅 샷 으로 실행 되므로 ( 13 장 참조 ) 대상 테이블에 대한 서로의 영향을 "볼"수 없습니다.

각 문 RETURNING 절 에서 다른 CTE가 반환 한 내용을 볼 수 있습니다 . 그러나 기본 테이블은 모두 동일하게 보입니다.

당신 이하려는 일에 대해 두 가지 진술 (단일 트랜잭션으로)이 필요합니다. 주어진 예제는 실제로 하나의 단일이어야 INSERT하지만 단순화 된 예제 때문일 수 있습니다.


15

이것은 구현 결정입니다. Postgres 설명서, WITH쿼리 (공통 테이블 식)에 설명되어 있습니다. 이 문제와 관련된 두 개의 단락이 있습니다.

첫째, 관찰 된 행동의 이유 :

의 하위 문은 WITH동시에 실행되는 서로 메인 쿼리 . 따라서에서 데이터 수정 문 WITH을 사용할 때 지정된 업데이트가 실제로 발생하는 순서는 예측할 수 없습니다. 모든 명령문은 동일한 스냅 샷으로 실행되므로 (13 장 참조) 대상 테이블에 대한 서로의 영향을 "볼"수 없습니다. 이는 실제 행 갱신 순서의 예측 불가능의 영향을 완화 시키며, 데이터가 다른 하위 명령문과 기본 쿼리 간의 변경 사항을 전달하는 유일한 방법 임을 의미 합니다. RETURNINGWITH이것의 예는 ...

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 개 이상) 명령문을 사용하는 명령문을 작성하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.