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_timetsrange) 중복을 허용하려고 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.