PostgreSQL의 중복 업데이트에 삽입 하시겠습니까?


644

몇 달 전에 나는 다음 구문을 사용하여 MySQL에서 한 번에 여러 업데이트를 수행하는 방법에 대해 스택 오버플로에 대한 답변을 배웠습니다.

INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);

나는 이제 PostgreSQL로 전환했으며 분명히 이것이 맞지 않습니다. 올바른 모든 테이블을 참조하므로 다른 키워드가 사용된다고 가정하지만 PostgreSQL 설명서에서 이것이 어디에 있는지 잘 모르겠습니다.

명확히하기 위해 여러 항목을 삽입하고 이미 존재하는 경우 업데이트하고 싶습니다.


38
이 질문을 발견 한 사람은 Depesz의 기사 "왜 왜 그렇게 복잡한가?" . 문제와 가능한 해결책을 매우 잘 설명합니다.
Craig Ringer

8
UPSERT는 포스트 그레스 9.5에 추가됩니다 wiki.postgresql.org/wiki/...
tommed

4
@tommed-완료되었습니다 : stackoverflow.com/a/34639631/4418
warren

답변:


515

버전 9.5 이후의 PostgreSQL 에는 ON CONFLICT 과 함께 UPSERT 구문이 있습니다. 다음 구문으로 (MySQL과 유사)

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

"upsert"에 대한 postgresql의 이메일 그룹 아카이브를 검색 하면 매뉴얼에서 원하는 작업을 수행하는 예 를 찾을 수 있습니다 .

예 38-2. UPDATE / INSERT 예외

이 예제는 예외 처리를 사용하여 UPDATE 또는 INSERT를 적절하게 수행합니다.

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        -- note that "a" must be unique
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

9.1 이상의 CTE를 사용하여 해커 메일 링리스트 에이를 대량으로 수행하는 방법에 대한 예가있을 수 있습니다 .

WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;

보다 명확한 예는 a_horse_with_no_name의 답변 을 참조하십시오 .


7
내가 이것에 대해 싫어하는 유일한 점은 각 upsert가 데이터베이스에 대한 자체 호출이기 때문에 속도가 훨씬 느려진다는 것입니다.
baash05

@ baash05 대량으로 수행 할 수있는 방법이있을 수 있습니다. 업데이트 된 답변을 참조하십시오.
Stephen Denne

2
내가 다르게하는 유일한 방법은 LOOP 대신 FOR 1..2 LOOP을 사용하는 것입니다. 따라서 다른 고유 제한 조건을 위반하면 무기한으로 회전하지 않습니다.
olamork

2
무엇을 않습니다 excluded여기에 최초의 솔루션에 참조?
ichbinallen

2
문서 @ichbinallen ON CONFLICT DO UPDATE의 SET 및 WHERE 절은 테이블 이름 (또는 별명)을 사용하여 기존 행과 특수 제외 테이블을 사용하여 삽입을 제안 된 행에 액세스 할 수 있습니다 . 이 경우 특수 excluded테이블을 사용하면 처음에 삽입하려고했던 값에 액세스 할 수 있습니다.
TMichel

429

경고 : 여러 세션에서 동시에 실행하는 경우 안전하지 않습니다 (아래주의 사항 참조).


postgresql에서 "UPSERT"를 수행하는 또 다른 영리한 방법은 각각 성공하거나 효과가 없도록 설계된 두 개의 순차적 UPDATE / INSERT 문을 수행하는 것입니다.

UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
       SELECT 3, 'C', 'Z'
       WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);

"id = 3"인 행이 이미 존재하면 UPDATE가 성공합니다. 그렇지 않으면 효과가 없습니다.

INSERT는 "id = 3"인 행이 아직없는 경우에만 성공합니다.

이 두 가지를 단일 문자열로 결합하고 응용 프로그램에서 실행되는 단일 SQL 문으로 둘 다 실행할 수 있습니다. 단일 트랜잭션에서 함께 실행하는 것이 좋습니다.

이것은 단독으로 또는 잠긴 테이블에서 실행될 때 매우 잘 작동하지만 행이 동시에 삽입되면 여전히 중복 키 오류로 실패하거나 행이 동시에 삭제 될 때 행이 삽입되지 않고 종료 될 수 있음을 의미하는 경쟁 조건이 적용됩니다 . SERIALIZABLE의 PostgreSQL 9.1 이상에 거래는 많이 시도해야 의미 매우 높은 직렬화 실패율의 비용으로 안정적으로 처리합니다. 참조 upsert 너무 복잡 이유를 자세히이 경우에 대해 설명한다.

이 방법은 또한 에 손실 업데이트에 따라 read committed응용 프로그램을 확인하지 않는 한 영향을받는 행 수와를 검증 중 하나를 분리 insert또는 update영향을받는 행 .


6
짧은 대답 : 레코드가 존재하면 INSERT는 아무것도하지 않습니다. 긴 대답 : INSERT의 SELECT는 where 절과 일치하는만큼 많은 결과를 반환합니다. 최대 1 개 (1 번이 하위 선택 결과가 아닌 경우), 그렇지 않으면 0입니다. 따라서 INSERT는 하나 또는 0 개의 행을 추가합니다.
피터 베커

3
'where'부분은 다음을 사용하여 단순화 할 수 있습니다.... where not exists (select 1 from table where id = 3);
Endy Tjahjono

1
이것은 정답 일 것입니다. 약간의 조정만으로도 대량 업데이트를 할 수 있습니다. 흠 .. 임시 테이블을 사용할 수 있는지 궁금합니다 ..
baash05

1
@keaplogik, 그 9.1 제한은 다른 답변에 설명되어있는 쓰기 가능한 CTE (공통 테이블 표현식)입니다. 이 답변에 사용 된 구문은 매우 기본적이며 오랫동안 지원되었습니다.
bovine

8
경고,이 손실 된 업데이트의 적용을받습니다 read committed응용 프로그램 검사가 확인 있는지 확인하는 않는 분리 insert또는이 update아닌 제로 행 개수가 있습니다. 참조 dba.stackexchange.com/q/78510/7788
크레이그 벨소리를

227

PostgreSQL 9.1에서는 쓰기 가능한 CTE ( 공통 테이블 표현식 )를 사용하여이를 달성 할 수 있습니다 .

WITH new_values (id, field1, field2) as (
  values 
     (1, 'A', 'X'),
     (2, 'B', 'Y'),
     (3, 'C', 'Z')

),
upsert as
( 
    update mytable m 
        set field1 = nv.field1,
            field2 = nv.field2
    FROM new_values nv
    WHERE m.id = nv.id
    RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1 
                  FROM upsert up 
                  WHERE up.id = new_values.id)

다음 블로그 항목을 참조하십시오.


이 솔루션은 고유 키 위반을 방지 하지는 않지만 업데이트 손실에 취약하지 않습니다. dba.stackexchange.com에서 Craig Ringer
후속 조치를 참조하십시오.


1
@ FrançoisBeausoleil : 경쟁 조건의 가능성은 "시도 / 핸들 예외"접근 방식보다 훨씬 작습니다
a_horse_with_no_name

2
@a_horse_with_no_name 경쟁 조건의 가능성이 훨씬 작다는 것을 정확히 어떻게 의미합니까? 이 레코드를 동일한 레코드와 동시에 실행하면 쿼리가 레코드가 삽입되었음을 감지 할 때까지 "중복 키 값이 고유 제한 조건을 위반합니다"라는 오류가 100 % 발생합니다. 이것이 완전한 예입니까?
Jeroen van Dijk

4
@a_horse_with_no_name 솔루션은 upsert 문을 다음 잠금으로 랩핑 할 때 동시 상황에서 작동하는 것 같습니다. BEGIN WORK; 쉐어 행 독점 모드에서 잠금 테이블 mytable; <여기서 여기>; 커밋 작업;
Jeroen van Dijk

2
@JeroenvanDijk : 감사합니다. 내가 "훨씬 더 작다"는 의미는 이것에 대한 여러 트랜잭션 (그리고 변경 사항을 커밋!)이 모든 것이 단일 명령문이므로 업데이트와 삽입 사이의 시간 범위가 더 짧다는 것입니다. 두 개의 독립적 인 INSERT 문으로 pk 위반을 항상 생성 할 수 있습니다. 전체 테이블을 잠그면 테이블에 대한 모든 액세스를 직렬화 할 수 있습니다 (직렬화 가능한 격리 수준으로 달성 할 수있는 것).
a_horse_with_no_name 11

12
이 솔루션은 삽입 트랜잭션이 롤백되면 업데이트가 손실 될 수 있습니다. UPDATE행에 영향을 미쳤 는지 확인할 수 없습니다 .
Craig Ringer

132

PostgreSQL 9.5 이상에서는을 사용할 수 있습니다 INSERT ... ON CONFLICT UPDATE.

설명서를 참조하십시오 .

MySQL INSERT ... ON DUPLICATE KEY UPDATE은로 직접 표현할 수 있습니다 ON CONFLICT UPDATE. SQL 표준 구문도 아니며 데이터베이스 별 확장입니다. 이것을 위해 사용되지 않은 좋은 이유 MERGE가 있습니다 . 새로운 구문은 재미를 위해 만들어지지 않았습니다. (MySQL의 구문에는 직접 채택되지 않은 문제도 있습니다).

예 : 주어진 설정 :

CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);

MySQL 쿼리 :

INSERT INTO tablename (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

된다 :

INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;

차이점 :

  • 당신은 해야한다 고유성 검사에 사용하는 열 이름 (또는 고유 제한 조건 이름)을 지정합니다. 그게ON CONFLICT (columnname) DO

  • SET이것이 일반적인 UPDATE설명 인 것처럼 키워드를 사용해야합니다 .

멋진 기능도 있습니다.

  • 당신은 당신이 WHERE절을 가질 수 있습니다 UPDATE( 특정 가치 ON CONFLICT UPDATEON CONFLICT IGNORE위해 효과적으로 전환하도록 함 )

  • 삽입 제안 된 값은 EXCLUDED대상 변수 테이블과 동일한 구조를 갖는 row-variable로 사용 가능 합니다. 테이블 이름을 사용하여 테이블의 원래 값을 얻을 수 있습니다. 그래서이 경우에 EXCLUDED.c있을 것입니다 10및 (즉, 우리가 삽입하려고 무엇 때문에) "table".c될 것입니다 3그 테이블의 현재 값이 때문입니다. SET표현식과 WHERE절에 둘 중 하나 또는 둘 다를 사용할 수 있습니다 .

upsert에 대한 배경은 PostgreSQL 에서 UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) 방법을 참조하십시오 .


MySQL의 상태에서 자동 증가 필드에 차이가 발생했기 때문에 위에서 설명한대로 PostgreSQL의 9.5 솔루션을 살펴 보았습니다 ON DUPLICATE KEY UPDATE. Postgres 9.5를 다운로드하고 코드를 구현했지만 Postgres에서도 똑같은 문제가 발생합니다. 기본 키의 직렬 필드가 연속적이지 않습니다 (삽입과 업데이트 사이에 간격이 있습니다). 여기서 무슨 일이 일어나고 있습니까? 이것이 정상입니까? 이 행동을 피하는 방법에 대한 아이디어가 있습니까? 감사합니다.
WM

@WM 그것은 upsert 연산에 내재되어 있습니다. 삽입을 시도하기 전에 시퀀스를 생성하는 기능을 평가해야합니다. 이러한 시퀀스는 동시에 작동하도록 설계되었으므로 정상적인 트랜잭션 의미론에서 제외되지만, 하위 트랜잭션에서 생성이 롤백되지 않고 롤백되지 않더라도 정상적으로 완료되고 나머지 작업과 커밋됩니다. 따라서 이것은 "갭리스 (gapless)"시퀀스 구현에서도 발생합니다. DB가이를 피할 수있는 유일한 방법은 키 검사가 끝날 때까지 시퀀스 생성 평가를 지연시키는 것입니다.
Craig Ringer

1
자체 문제를 일으키는 @WM. 기본적으로, 당신은 붙어 있습니다. 그러나 serial / auto_increment에 차이가없는 경우 이미 버그가 있습니다. 지금에 의존하고, 부하 재부팅, 클라이언트 오류 중반 거래, 충돌 등 당신은 결코해야합니다 - 당신으로 인해 일시적인 오류를 포함하여 롤백에 시퀀스 간격을 가질 수 SERIAL/ SEQUENCE또는 AUTO_INCREMENT간격을 가지고 있지. 틈이없는 시퀀스가 ​​필요한 경우 더 복잡합니다. 일반적으로 카운터 테이블을 사용해야합니다. 구글이 더 알려줄 것이다. 그러나 틈이없는 시퀀스는 모든 인서트 동시성을 방지합니다.
Craig Ringer

@WM 갭리스 시퀀스와 업 서트가 절대적으로 필요한 경우 카운터 테이블을 사용하는 갭리스 시퀀스 구현과 함께 매뉴얼에 설명 된 함수 기반 업 서트 방식을 사용할 수 있습니다. BEGIN ... EXCEPTION ...오류시 롤백되는 서브 트랜잭션 에서 실행 되므로 INSERT실패 하면 시퀀스 증가분이 롤백됩니다 .
Craig Ringer

매우 유익한 @Craig Ringer에게 감사드립니다. 자동 증분 기본 키를 포기할 수 있다는 것을 깨달았습니다. 나는 3 필드의 복합 기본을 만들었고 현재의 특정 요구에 대해 갭리스 자동 증가 필드가 실제로 필요하지 않습니다. 다시 한 번 감사드립니다. 제공 한 정보는 향후 자연스럽고 건강한 DB 동작을 방지하려는 시간을 절약 해줍니다. 나는 지금 그것을 더 잘 이해합니다.
WM

17

나는 여기에 왔을 때 똑같은 것을 찾고 있었지만 일반적인 "upsert"함수가 부족하여 조금 신경 쓰지 않았기 때문에 업데이트를 전달하고 sql을 수동으로 해당 함수의 인수로 삽입 할 수 있다고 생각했습니다.

그것은 다음과 같이 보일 것입니다 :

CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
    RETURNS VOID
    LANGUAGE plpgsql
AS $$
BEGIN
    LOOP
        -- first try to update
        EXECUTE sql_update;
        -- check if the row is found
        IF FOUND THEN
            RETURN;
        END IF;
        -- not found so insert the row
        BEGIN
            EXECUTE sql_insert;
            RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- do nothing and loop
        END;
    END LOOP;
END;
$$;

그리고 아마도 "upsert"를 배치하기 위해 처음에하고 싶었던 일을하기 위해 Tcl을 사용하여 sql_update를 분할하고 개별 업데이트를 반복 할 수 있습니다. http://archives.postgresql.org/pgsql- performance / 2006-04 / msg00557.php

가장 높은 비용은 코드에서 쿼리를 실행하는 것이며, 데이터베이스 측면에서는 실행 비용이 훨씬 적습니다.


3
여전히 재시도 루프에서 이것을 실행해야하며 DELETE, 테이블을 잠 그거나 SERIALIZABLEPostgreSQL 9.1 이상 에서 트랜잭션 격리 상태에 있지 않으면 동시와 경쟁하기 쉽습니다 .
Craig Ringer

13

간단한 명령은 없습니다.

가장 올바른 방법은 docs 와 같은 기능을 사용하는 것입니다 .

또 다른 해결책은 (안전하지는 않지만) 리턴으로 업데이트하고 업데이트 된 행을 확인하고 나머지 행을 삽입하는 것입니다

다음과 같은 내용이 있습니다.

update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;

id : 2가 리턴되었다고 가정하십시오.

insert into table (id, column) values (1, 'aa'), (3, 'cc');

물론 여기에 명확한 경쟁 조건이 있기 때문에 조만간 (동시 환경에서) 구제되지만 일반적으로 작동합니다.

여기의 주제에 더 길고 더 포괄적 인 문서 .


1
이 옵션을 사용하는 경우 업데이트가 아무 것도 수행하지 않아도 ID가 반환되는지 확인하십시오. "Update table foo set bar = 4 where bar = 4"와 같은 데이터베이스 최적화 쿼리를 보았습니다.
thelem

10

개인적으로 insert 문에 첨부 된 "rule"을 설정했습니다. 시간당 고객 당 dns 적중을 기록한 "dns"테이블이 있다고 가정하십시오.

CREATE TABLE dns (
    "time" timestamp without time zone NOT NULL,
    customer_id integer NOT NULL,
    hits integer
);

업데이트 된 값으로 행을 다시 삽입하거나 존재하지 않는 경우 작성하려고했습니다. customer_id와 시간을 입력했습니다. 이 같은:

CREATE RULE replace_dns AS 
    ON INSERT TO dns 
    WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
            AND (dns.customer_id = new.customer_id)))) 
    DO INSTEAD UPDATE dns 
        SET hits = new.hits 
        WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));

업데이트 : unique_violation 예외가 발생하므로 동시 삽입이 발생하면 실패 할 수 있습니다. 그러나 종료되지 않은 트랜잭션은 계속되고 성공하므로 종료 된 트랜잭션을 반복하면됩니다.

그러나 항상 많은 수의 삽입이 발생하는 경우 삽입 명령문 주위에 테이블 잠금을 설정하려고합니다. SHARE ROW EXCLUSIVE 잠금은 대상 테이블에서 행을 삽입, 삭제 또는 업데이트 할 수있는 조작을 방지합니다. 그러나 고유 키를 업데이트하지 않는 업데이트는 안전하므로 아무 조작도하지 않으면 권고 잠금을 대신 사용하십시오.

또한 COPY 명령은 RULES를 사용하지 않으므로 COPY를 사용하여 삽입하는 경우 대신 트리거를 사용해야합니다.


9

이 함수 병합을 사용합니다

CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
  RETURNS void AS
$BODY$
BEGIN
    IF EXISTS(SELECT a FROM tabla WHERE a = key)
        THEN
            UPDATE tabla SET b = data WHERE a = key;
        RETURN;
    ELSE
        INSERT INTO tabla(a,b) VALUES (key, data);
        RETURN;
    END IF;
END;
$BODY$
LANGUAGE plpgsql

1
단순히 update첫 번째 작업을 수행 한 다음 업데이트 된 행 수를 확인하는 것이 더 효율적 입니다. (Ahmad의 답변 참조)
a_horse_with_no_name

8

INSERT AND REPLACE하려는 경우 위의 사용자 정의 "upsert"기능을 사용하십시오.

`

 CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)

 RETURNS void AS
 $BODY$
 BEGIN
    -- first try to insert and after to update. Note : insert has pk and update not...

    EXECUTE sql_insert;
    RETURN;
    EXCEPTION WHEN unique_violation THEN
    EXECUTE sql_update; 
    IF FOUND THEN 
        RETURN; 
    END IF;
 END;
 $BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;
 ALTER FUNCTION upsert(text, text)
 OWNER TO postgres;`

실행 후 다음과 같이하십시오.

SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)

컴파일러 오류를 피하려면 이중 달러 쉼표를 사용해야합니다

  • 속도를 확인하십시오 ...

7

가장 좋아하는 답변과 비슷하지만 약간 더 빠르게 작동합니다.

WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)

(출처 : http://www.the-art-of-web.com/sql/upsert/ )


3
두 개의 세션에서 동시에 실행하면 업데이트가 기존 행을 볼 수 없으므로 두 업데이트가 모두 0 행에 도달하므로 두 쿼리 모두 삽입을 실행하기 때문에 실패합니다.
Craig Ringer

6

계정 설정을 관리 할 때 이름 값 쌍과 동일한 문제가 있습니다. 디자인 기준은 클라이언트마다 설정이 다를 수 있다는 것입니다.

JWP와 비슷한 내 솔루션은 대량 지우기 및 교체하여 응용 프로그램 내에서 병합 레코드를 생성하는 것입니다.

이것은 방탄, 플랫폼 독립적이며 클라이언트 당 약 20 개 이상의 설정이 없기 때문에 3 개의 상당히 낮은로드 db 호출입니다. 아마도 가장 빠른 방법입니다.

개별 행을 업데이트 (예외를 확인한 후 삽입)하거나 일부 조합을 업데이트하는 대안은 끔찍한 코드이며 느리고 종종 위에서 언급 한 것처럼 비표준 SQL 예외 처리가 db에서 db로 또는 심지어 릴리스마다 변경되기 때문에 중단됩니다.

 #This is pseudo-code - within the application:
 BEGIN TRANSACTION - get transaction lock
 SELECT all current name value pairs where id = $id into a hash record
 create a merge record from the current and update record
  (set intersection where shared keys in new win, and empty values in new are deleted).
 DELETE all name value pairs where id = $id
 COPY/INSERT merged records 
 END TRANSACTION

SO에 오신 것을 환영합니다. 좋은 소개! :-)
Don Question

1
이것은 더 비슷 REPLACE INTO이상의 INSERT INTO ... ON DUPLICATE KEY UPDATE트리거를 사용하는 경우 문제가 발생할 수있다. 업데이트보다는 삭제 및 삽입 트리거 / 규칙을 실행하게됩니다.
cHao

5

명령문PostgreSQL 문서에INSERT 따르면 ON DUPLICATE KEY케이스 처리는 지원되지 않습니다. 구문의 해당 부분은 독점적 인 MySQL 확장입니다.


@Lucian MERGE은 실제로 OLAP 작업에 더 가깝습니다 . 설명 은 stackoverflow.com/q/17267417/398670 을 참조하십시오 . 그것은 동시성 의미론을 정의하지 않으며 upsert에 그것을 사용하는 대부분의 사람들은 단지 버그를 만들고 있습니다.
Craig Ringer

5
CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
  RETURNS boolean AS
$BODY$
BEGIN
    UPDATE users SET name = _name WHERE id = _id;
    IF FOUND THEN
        RETURN true;
    END IF;
    BEGIN
        INSERT INTO users (id, name) VALUES (_id, _name);
    EXCEPTION WHEN OTHERS THEN
            UPDATE users SET name = _name WHERE id = _id;
        END;
    RETURN TRUE;
END;

$BODY$
  LANGUAGE plpgsql VOLATILE STRICT

5

작은 세트를 병합 할 때는 위 기능을 사용하는 것이 좋습니다. 그러나 많은 양의 데이터를 병합하는 경우 http://mbk.projects.postgresql.org 를 참조 하십시오.

내가 아는 현재 모범 사례는 다음과 같습니다.

  1. 새로운 / 업데이트 된 데이터를 임시 테이블로 복사합니다 (비용이 괜찮다면 삽입하거나 삽입 할 수 있습니다).
  2. 잠금 획득 [선택 사항] (테이블 잠금, IMO보다 권장)
  3. 병합 (재미있는 부분)

5

UPDATE는 수정 된 행 수를 반환합니다. JDBC (Java)를 사용하는 경우이 값을 0에 대해 검사하고 영향을받은 행이 없으면 대신 INSERT를 실행하십시오. 다른 프로그래밍 언어를 사용하는 경우 수정 된 행 수를 여전히 얻을 수있는 경우 설명서를 확인하십시오.

이것은 우아하지는 않지만 호출 코드에서 사용하기가 훨씬 간단한 SQL이 훨씬 간단합니다. 이와 달리 PL / PSQL로 10 줄 스크립트를 작성하는 경우에는 단독으로 하나 또는 다른 종류의 단위 테스트를 수행해야합니다.


4

편집 : 예상대로 작동하지 않습니다. 허용되는 답변과 달리 두 프로세스가 upsert_foo동시에 호출 할 때 고유 키 위반이 발생 합니다.

유레카! 하나의 쿼리에서 수행하는 방법을 찾았습니다. UPDATE ... RETURNING행이 영향을 받았는지 테스트 하는 데 사용하십시오 .

CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);

CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
    UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;

CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
    INSERT INTO foo
        SELECT $1, $2
        WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;

UPDATE, 불행하게도,이 구문 오류 때문에 별도의 절차에서 수행해야합니다 :

... WHERE NOT EXISTS (UPDATE ...)

이제 원하는대로 작동합니다.

SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');

1
쓰기 가능한 CTE를 사용하는 경우 하나의 명령문으로 결합 할 수 있습니다. 그러나 여기에 게시 된 대부분의 솔루션과 마찬가지로이 방법은 잘못되어 동시 업데이트가있을 경우 실패합니다.
Craig Ringer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.