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


94

나는 테이블에 두 개의 열을 가지고 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 
....

4
"col1, col2, 둘 다 고유 인덱스입니다." 그것은 col1이 고유하고 col2가 고유하거나 col1, col2의 조합이 고유하다는 것을 의미합니까?
— e4c5

1
않음을 의미 COL1은 고유하고 COL2가 개별적으로 고유 한
— 오토 Shavadze

답변:


48

샘플 테이블 및 데이터

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');

3
이것이 작동하는 방식이지만 필요한 것보다 약간 더 많은 작업 / 논리가 필요합니다. 실제로해야 할 일은 두 열에 고유 한 제약 조건을 만드는 것입니다. 아래 내 대답을 참조하십시오.
— Jubair

한 번에 여러 세트의 VALUES를 삽입하는 경우에도 merge_db 솔루션을 사용할 수 있습니까?
— daniyel

당신은 저장 기능을 다시 작성해야합니다 @daniyel
— e4c5

3
구식 upsert 사용을 제안하는 것이 얼마나 유용한 지 명확하지 않습니다.이 질문은 "postgres upsert 9.5"에 대해 잘 참조되며 모든 constraint_names 옵션과 함께 사용하는 방법을 설명하면 더 나을 수 있습니다.
— Pak

3
@Pak 질문을 명확하게 읽지 않았기 때문에 명확하지 않습니다. op는 해당 필드에서 복합 키를 찾지 않습니다. 다른 대답은 복합 키에 대해 작동합니다
— e4c5

65

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_time이 tsrange) 중복을 허용하려고 id하지만 겹치는 기간에 대해서는 허용 하지 않는다고 가정합니다. 고유 제한 조건은 도움이되지 않습니다,하지만 제외 제약 조건 당신은 말할 수있다 "그들의 경우 새로운 레코드를 제외 id등호 오래된 id또한 및 valid_time중복 그것 valid_time."


4
이것이 생성하는 것은 함께 고유 인덱스 생성 고유 인덱스 idx_t_id_a on t (id, a); 물론 OP는 두 열이 개별적으로 또는 함께 고유한지 여부를 명확하게 설명하지 않습니다.
— e4c5

왜 postgres는 인덱스 이름을 따서 명명 된 열이 없다고 말하고 사용하지 못 ON CONFLICT합니까?
— Pak

@Pak은 사용중인 특정 명령과 수신 한 오류 메시지로 직접 질문을 작성해야하는 것 같습니다.
— Paul A Jungwirth

@PaulAJungwirth 모르겠습니다. 귀하의 대답은 on conflict명령에 대한 제약 조건으로 고유 인덱스 입니다. 오류는 "my_index_name 열이 존재하지 않습니다"입니다.
— 박

OP가 요청한대로 각 열에 대해 별도의 고유 제약 조건을 사용하여 어쨌든 이것을 시도했지만 작동하지 않았습니다. 내가 기대했던 것은 아니지만 나는 바라고 있었다.
— sudo

5

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



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

또는 / 그리고

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

1

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

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

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

4
죄송하지만 질문을 오해하셨습니다. OP는 함께 고유 한 제약 조건을 원하지 않습니다.
— e4c5

1

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

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

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


3
이 두 열에 대한 고유 인덱스를 만들고 on conflict.
— Kishore Relangi

0

일반적으로 on conflict삽입하는 것에 대해 관련성이있는 유일한 제약 조건을 지정 하는 문만있는 문을 생성 할 수 있습니다.

일반적으로 한 번에 하나의 제약 만 "관련"제약이기 때문입니다. (많은 경우, 뭔가 이상하거나 이상하게 설계된 것인지 궁금합니다. 흠.)

예 :
(라이선스 : CC0 아님 , CC-By 만)

// there're these unique constraints:
//   unique (site_id, people_id, page_id)
//   unique (site_id, people_id, pages_in_whole_site)
//   unique (site_id, people_id, pages_in_category_id)
// and only *one* of page-id, category-id, whole-site-true/false
// can be specified. So only one constraint is "active", at a time.

val thingColumnName = thingColumnName(notfificationPreference)

val insertStatement = s"""
  insert into page_notf_prefs (
    site_id,
    people_id,
    notf_level,
    page_id,
    pages_in_whole_site,
    pages_in_category_id)
  values (?, ?, ?, ?, ?, ?)
  -- There can be only one on-conflict clause.
  on conflict (site_id, people_id, $thingColumnName)   <—— look
  do update set
    notf_level = excluded.notf_level
  """

val values = List(
  siteId.asAnyRef,
  notfPref.peopleId.asAnyRef,
  notfPref.notfLevel.toInt.asAnyRef,
  // Only one of these is non-null:
  notfPref.pageId.orNullVarchar,
  if (notfPref.wholeSite) true.asAnyRef else NullBoolean,
  notfPref.pagesInCategoryId.orNullInt)

runUpdateSingleRow(insertStatement, values)

과:

private def thingColumnName(notfPref: PageNotfPref): String =
  if (notfPref.pageId.isDefined)
    "page_id"
  else if (notfPref.pagesInCategoryId.isDefined)
    "pages_in_category_id"
  else if (notfPref.wholeSite)
    "pages_in_whole_site"
  else
    die("TyE2ABK057")

이 on conflict절은 내가하려는 작업에 따라 동적으로 생성됩니다. 페이지에 대해 알림 기본 설정을 삽입하는 경우 site_id, people_id, page_id제약 조건 에 고유 한 충돌이있을 수 있습니다 . 그리고 카테고리에 대한 알림 환경 설정을 구성하는 경우 위반 될 수있는 제약 조건이 site_id, people_id, category_id.

그래서 나는 내가 on conflict (... columns )무엇을 하고 싶은지 알기 때문에 올바른을 생성 할 수 있고, 당신도 마찬가지 일 것입니다.


-4

ON CONFLICT는 매우 서투른 솔루션입니다.

UPDATE dupes SET key1=$1, key2=$2 where key3=$3    
if rowcount > 0    
  INSERT dupes (key1, key2, key3) values ($1,$2,$3);

Oracle, Postgres 및 기타 모든 데이터베이스에서 작동


원 자성이 아니므로 동시에 여러 연결이있는 경우 실패하고 잘못된 결과를 생성 할 수 있습니다.
— Bogdan Mart
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.