[postgresql] ON CONFLICT 절에서 여러 충돌 대상 사용

나는 테이블에 두 개의 열을 가지고 col1, col2그들은 모두 고유 인덱스 (col1의 고유 그래서 COL2입니다)입니다.

이 테이블에 삽입하고 ON CONFLICT구문을 사용 하고 다른 열을 업데이트해야하지만 conflict_target절 에서 두 열을 모두 사용할 수는 없습니다 .

효과가있다:

INSERT INTO table
...
ON CONFLICT ( col1 )
DO UPDATE
SET
-- update needed columns here

그러나 다음과 같이 여러 열에 대해이를 수행하는 방법 :

...
ON CONFLICT ( col1, col2 )
DO UPDATE
SET
....



답변

샘플 테이블 및 데이터

CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
   CONSTRAINT col2_unique UNIQUE (col2)
);

INSERT INTO dupes values(1,1,'a'),(2,2,'b');

문제 재현

INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2

이것을 Q1이라고합시다. 결과는

ERROR:  duplicate key value violates unique constraint "col2_unique"
DETAIL:  Key (col2)=(2) already exists.

무엇 설명서를 말한다

충돌 대상은 고유 인덱스 추론을 수행 할 수 있습니다. 추론을 수행 할 때 하나 이상의 index_column_name 열 및 / 또는 index_expression 식과 선택적 index_predicate로 구성됩니다. 순서에 관계없이 정확히 충돌 대상 지정 열 / 표현식을 포함하는 모든 table_name 고유 인덱스는 중재자 인덱스로 추론 (선택)됩니다. index_predicate가 지정되면 추론을위한 추가 요구 사항으로 중재자 인덱스를 충족해야합니다.

이것은 다음 쿼리가 작동해야한다는 인상을 주지만 실제로는 col1과 col2에 대한 고유 인덱스가 함께 필요하기 때문이 아닙니다. 그러나 이러한 인덱스는 col1 및 col2가 OP의 요구 사항 중 하나 인 개별적으로 고유하다는 것을 보장하지 않습니다.

INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2

이 쿼리를 Q2라고 부르겠습니다 (이는 구문 오류로 실패 함).

왜?

Postgresql은 두 번째 열에서 충돌이 발생할 때 발생해야하는 일이 잘 정의되어 있지 않기 때문에 이러한 방식으로 작동합니다. 많은 가능성이 있습니다. 예를 들어 위의 Q1 쿼리에서 col1충돌이있을 때 postgresql을 업데이트해야 col2합니까? 그러나 그것이 또 다른 갈등으로 이어진다면 col1? postgresql이 어떻게 처리 할 것으로 예상됩니까?

해결책

해결책은 ON CONFLICT와 구식 UPSERT 를 결합하는 것입니다 .

CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
        IF found THEN
            RETURN;
        END IF;

        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently, or key2
        -- already exists in col2,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            BEGIN
                INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
                RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- Do nothing, and loop to try the UPDATE again.
            END;
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

이 저장된 함수의 논리를 수정하여 원하는 방식으로 열을 정확하게 업데이트해야합니다. 다음과 같이 호출하십시오.

SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');


답변

ON CONFLICT충돌 감지를 수행하려면 고유 색인 *이 필요합니다. 따라서 두 열에 고유 인덱스를 생성하기 만하면됩니다.

t=# create table t (id integer, a text, b text);
CREATE TABLE
t=# create unique index idx_t_id_a on t (id, a);
CREATE INDEX
t=# insert into t values (1, 'a', 'foo');
INSERT 0 1
t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar';
INSERT 0 1
t=# select * from t;
 id | a |  b
----+---+-----
  1 | a | bar

* 고유 인덱스 외에도 제외 제약 조건 을 사용할 수도 있습니다 . 이것들은 고유 한 제약보다 좀 더 일반적입니다. 테이블에 id및에 대한 열이 있고 valid_time(및 valid_timetsrange) 중복을 허용하려고 id하지만 겹치는 기간에 대해서는 허용 하지 않는다고 가정합니다. 고유 제한 조건은 도움이되지 않습니다,하지만 제외 제약 조건 당신은 말할 수있다 “그들의 경우 새로운 레코드를 제외 id등호 오래된 id또한 및 valid_time중복 그것 valid_time.”


답변

요즘에는 불가능합니다. ON CONFLICT 구문 의 마지막 버전은 절을 반복하는 것을 허용하지 않으며 CTE 를 사용하는 것도 가능하지 않습니다. 더 많은 충돌 대상을 추가하기 위해 ON CONFLICT에서 INSERT를 breack 할 수 없습니다.


답변

postgres 9.5를 사용하는 경우 EXCLUDED 공간을 사용할 수 있습니다.

PostgreSQL 9.5의 새로운 기능에서 가져온 예 :

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1)
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;


답변

  1. 제약 조건을 만듭니다 (예 : 외부 인덱스).

또는 / 그리고

  1. 기존 제약 조건 (psq의 \ d)을 살펴보십시오.
  2. INSERT 절에서 ON CONSTRAINT (constraint_name)을 사용하십시오.


답변

Vlad는 올바른 아이디어를 얻었습니다.

먼저 열에 테이블 고유 제약 조건을 만들어야합니다. col1, col2 그런 다음 그렇게하면 다음을 수행 할 수 있습니다.

INSERT INTO dupes values(3,2,'c')
ON CONFLICT ON CONSTRAINT dupes_pkey
DO UPDATE SET col3 = 'c', col2 = 2


답변

일종의 해키이지만 col1과 col2의 두 값을 새 열인 col3 (둘의 인덱스와 비슷 함)에 연결하여이 문제를 해결하고 그와 비교했습니다. 이것은 col1과 col2를 모두 일치시켜야하는 경우에만 작동합니다.

INSERT INTO table
...
ON CONFLICT ( col3 )
DO UPDATE
SET
-- update needed columns here

여기서 col3은 col1과 col2의 값 연결입니다.