ON CONFLICT DO UPDATE
행동을 명확하게
여기서 매뉴얼을 고려하십시오 .
삽입을 위해 제안 된 각 개별 행에 대해 삽입이 진행되거나,에 의해 지정된 중재자 구속 조건 또는 색인
conflict_target
이 위반되는 경우 대안 conflict_action
이 사용됩니다.
대담한 강조 광산. 당신은에서 고유 인덱스에 포함 된 컬럼에 대한 조건을 반복 할 필요가 없습니다 WHERE
받는 절 UPDATE
합니다 ( conflict_action
)
INSERT INTO test_upsert AS tu
(name , status, test_field , identifier, count)
VALUES ('shaun', 1 , 'test value', 'ident' , 1)
ON CONFLICT (name, status, test_field) DO UPDATE
SET count = tu.count + 1;
WHERE tu.name = 'shaun' AND tu.status = 1 AND tu.test_field = 'test value'
고유 한 위반은 이미 추가 된 WHERE
조항이 중복으로 적용 할 내용을 설정합니다 .
부분 인덱스 명확화
당신이 언급 한 것처럼 WHERE
실제 부분 인덱스 로 만들기 위해 절을 추가 하십시오 (그러나 반전 된 논리로).
CREATE UNIQUE INDEX test_upsert_partial_idx
ON public.test_upsert (name, status)
WHERE test_field IS NULL; -- not: "is not null"
UPSERT에서이 부분 인덱스 를 사용 하려면 @ypercube와 같이 일치하는 것이 필요합니다 .conflict_target
ON CONFLICT (name, status) WHERE test_field IS NULL
이제 상기 부분 인덱스가 추론됩니다. 그러나 매뉴얼에 다음 과 같이 언급되어 있습니다 .
[...] ON CONFLICT
다른 모든 기준을 만족하는 인덱스가 사용 가능한 경우 비 부분 고유 인덱스 (조건자가없는 고유 인덱스)가 유추됩니다 (따라서 사용 ).
추가 (또는 유일한) 인덱스가있는 (name, status)
경우 인덱스 도 사용됩니다. 의 인덱스 (name, status, test_field)
는 명시 적 으로 유추 되지 않습니다 . 이것은 문제를 설명하지는 않지만 테스트하는 동안 혼란을 가중시킬 수 있습니다.
해결책
AIUI, 위의 어느 것도 귀하의 문제를 해결하지 못합니다 . 부분 인덱스를 사용하면 NULL 값이 일치하는 특수한 경우 만 포착됩니다. 일치하는 다른 고유 인덱스 / 제약 조건이 없으면 다른 중복 행이 삽입되거나 그렇지 않으면 예외가 발생합니다. 나는 그것이 당신이 원하는 것이 아니라고 생각합니다. 당신은 쓰기:
복합 키는 20 개의 열로 구성되며 그 중 10 개는 널 입력 가능합니다.
정확히 무엇을 복제본으로 간주합니까? Postgres (SQL 표준에 따름)는 두 개의 NULL 값이 같은 것으로 간주하지 않습니다. 매뉴얼 :
일반적으로, 테이블에 제한 조건에 포함 된 모든 열의 값이 동일한 행이 둘 이상 있으면 고유 제한 조건이 위반됩니다. 그러나이 비교에서 두 개의 널값은 동일한 것으로 간주되지 않습니다. 즉, 고유 제한 조건이 존재하는 경우에도 제한된 열 중 하나에 널값을 포함하는 중복 행을 저장할 수 있습니다. 이 동작은 SQL 표준을 준수하지만 다른 SQL 데이터베이스가이 규칙을 따르지 않을 수 있다고 들었습니다. 따라서 이식성이 뛰어난 응용 프로그램을 개발할 때는주의하십시오.
관련 :
NULL
10 개의 nullable 열의 값을 모두 동일한 것으로 간주 한다고 가정 합니다. 여기에 설명 된 것처럼 추가 부분 인덱스로 단일 nullable 열을 덮는 것이 우아하고 실용적입니다.
그러나 더 많은 널 입력 가능 열의 경우 신속하게 처리되지 않습니다. 널 입력 가능 컬럼의 모든 고유 조합에 대해 부분 인덱스가 필요합니다. 만 2 세 부분 인덱스의 그 사람들의 위해 (a)
, (b)
그리고 (a,b)
. 의 수가 기하 급수적으로 증가하고 있습니다 2^n - 1
. 10 개의 널 입력 가능 열에 대해 가능한 모든 NULL 값 조합을 포함하려면 이미 1023 개의 부분 인덱스가 필요합니다. 안돼
간단한 해결책 : NULL 값을 대체하고 관련 열을 정의 NOT NULL
하면 간단한 UNIQUE
제약 조건으로 모든 것이 잘 작동 합니다.
이것이 옵션이 아닌 경우 색인 COALESCE
에서 NULL을 대체 하는 표현식 색인을 제안합니다 .
CREATE UNIQUE INDEX test_upsert_solution_idx
ON test_upsert (name, status, COALESCE(test_field, ''));
빈 문자열 ( ''
) 문자 유형에 대한 명백한 후보이지만, 당신이 사용할 수있는 어떤 표시하거나에 따라 NULL로 접을 수 있습니다 결코 법적 값을 당신의 "독특한"의 정의.
그런 다음이 문장을 사용하십시오.
INSERT INTO test_upsert as tu(name,status,test_field,identifier, count)
VALUES ('shaun', 1, null , 'ident', 11) -- works with
, ('bob' , 2, 'test value', 'ident', 22) -- and without NULL
ON CONFLICT (name, status, COALESCE(test_field, '')) DO UPDATE -- match expr. index
SET count = COALESCE(tu.count + EXCLUDED.count, EXCLUDED.count, tu.count);
@ ypercube와 마찬가지로 실제로 count
기존 카운트 에 추가하고 싶다고 가정합니다 . 열이 NULL 일 수 있으므로 NULL을 추가하면 열이 NULL로 설정됩니다. 을 정의 count NOT NULL
하면 단순화 할 수 있습니다.
또 다른 아이디어는 모든 고유 한 위반 을 다루기 위해 statement 에서 conflict_target 을 삭제하는 것 입니다. 그런 다음 "고유 한"것으로보다 정교한 정의를 위해 다양한 고유 색인을 정의 할 수 있습니다. 그러나 그것은와 함께 날지 않을 것입니다 . 매뉴얼을 한 번 더 :ON CONFLICT DO UPDATE
의 경우 ON CONFLICT DO NOTHING
conflict_target을 지정하는 것은 선택 사항입니다. 생략하면 사용 가능한 모든 제약 조건 (및 고유 인덱스)과의 충돌이 처리됩니다. 의 경우 ON CONFLICT DO UPDATE
conflict_target 을 제공 해야 합니다.
count = CASE WHEN EXCLUDED.count IS NULL THEN tu.count ELSE COALESCE(tu.count, 0) + COALESCE(EXCLUDED.count, 0) END
같이 단순화 할 수 있습니다count = COALESCE(tu.count+EXCLUDED.count, EXCLUDED.count, tu.count)