문제

몇 달 전에 나는 다음 구문을 사용하여 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 문서에서 어디에 있는지 잘 모르겠습니다.

명확히하기 위해, 나는 몇 가지를 삽입하고 이미 업데이트하기 위해 존재하는 경우를 삽입하고 싶습니다.

도움이 되었습니까?

해결책

버전 9.5 이후 Postgresql 상향 구문 갈등에 절. 다음 구문 (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. 업데이트/인서트가있는 예외

이 예제는 예외 처리를 사용하여 업데이트 또는 삽입을 수행합니다.

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의 답변 명확한 예를 위해.

다른 팁

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


PostgreSQL에서 "Upsert"를 수행하는 또 다른 영리한 방법은 각각 성공하거나 영향을 미치지 않도록 설계된 두 개의 순차적 업데이트/삽입 문을 수행하는 것입니다.

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"이 이미 존재하면 업데이트가 성공합니다. 그렇지 않으면 효과가 없습니다.

"ID = 3"이있는 행이 아직 존재하지 않는 경우에만 인서트가 성공합니다.

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

이것은 분리되어 있거나 잠긴 테이블에서 실행될 때 매우 잘 작동하지만 행이 동시에 삽입되거나 행이 동시에 삽입 될 때 행이 삽입되지 않은 상태에서 종료 될 수있는 경우 중복 키 오류로 여전히 실패 할 수 있음을 의미합니다. . ㅏ SERIALIZABLE PostgreSQL 9.1 이상의 거래는 매우 높은 직렬화 실패율의 비용으로 안정적으로 처리 할 것이므로 많은 재 시도가 필요합니다. 보다 Upsert가 그렇게 복잡한 이유는 무엇입니까?, 이 경우에 대해 자세히 설명합니다.

이 접근법도 있습니다 업데이트 손실이 적용됩니다 read committed 응용 프로그램이 영향을받는 행 계산을 확인하고 insert 아니면 그 update 줄에 영향을 미쳤습니다.

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의 후속 조치

PostgreSQL 9.5 및 최신에서 사용할 수 있습니다 INSERT ... ON CONFLICT UPDATE.

보다 문서.

MySQL INSERT ... ON DUPLICATE KEY UPDATE 직접적으로 a ON CONFLICT UPDATE. SQL-Standard 구문도 마찬가지이며 둘 다 데이터베이스 별 확장자입니다. 좋은 이유가 있습니다 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 UPDATE ~ 안으로 ON CONFLICT IGNORE 특정 값에 대해)

  • 제안 된 삽입 값은 행 변수로 사용할 수 있습니다 EXCLUDED, 대상 테이블과 동일한 구조를 갖습니다. 테이블 이름을 사용하여 테이블에서 원래 값을 얻을 수 있습니다. 이 경우 EXCLUDED.c 될거야 10 (우리가 삽입하려고했던 것이기 때문에) "table".c 될거야 3 그것이 테이블의 현재 값이기 때문입니다. 당신은 두 가지 또는 둘 다를 사용할 수 있습니다 SET 표현 및 WHERE 절.

Upsert에 대한 배경은 참조하십시오 PostgreSQL에서 Upsert (병합, 삽입 ... 중복 업데이트) 방법은 무엇입니까?

나는 여기에 왔을 때 같은 것을 찾고 있었지만 일반적인 "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

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

그것을하는 간단한 명령은 없습니다.

가장 올바른 접근법은 기능을 사용하는 것입니다. 문서.

또 다른 솔루션 (안전하지는 않지만)은 리턴으로 업데이트하고 업데이트 한 행을 확인하고 나머지 부분을 삽입하는 것입니다.

라인을 따라 무언가 :

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

물론 여기에는 명확한 인종 조건이 있기 때문에 조만간 (동시 환경에서) 구제 할 것입니다. 그러나 일반적으로 작동합니다.

여기에 있습니다 주제에 대한 더 길고 포괄적 인 기사.

개인적으로 삽입 문에 첨부 된 "규칙"을 설정했습니다. 시간당 고객 당 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));

업데이트 : 동시 인서트가 발생하면 고유 한 예외를 생성하므로 이는 실패 할 가능성이 있습니다. 그러나 비 말단 거래는 계속되고 성공할 것이며 종료 된 거래를 반복하면됩니다.

그러나 항상 많은 삽입물이 발생하는 경우 삽입 문에 테이블 잠금 장치를 넣을 것입니다. 그러나 고유 한 키를 업데이트하지 않는 업데이트는 안전하므로 작업이 없으면 대신 자문 잠금 장치를 사용하십시오.

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

삽입하고 교체하려는 경우 위의 "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... $$)

컴파일러 오류를 피하기 위해 이중 달러 규모를 넣는 것이 중요합니다.

  • 속도 확인 ...

대부분의 대답과 유사하지만 약간 더 빠르게 작동합니다.

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-web.com/sql/upsert/)

계정 설정을 이름 값 쌍으로 관리하는 데 동일한 문제가 있습니다. 설계 기준은 다른 클라이언트마다 다른 설정 세트를 가질 수 있다는 것입니다.

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

에 따라 Postgresql 문서 INSERT 성명, 처리 ON DUPLICATE KEY 사례는 지원되지 않습니다. 구문의 일부는 독점적 인 MySQL 확장입니다.

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

이 기능 병합을 사용합니다

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

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

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

  1. 새/업데이트 된 데이터를 온도 테이블에 복사하십시오 (물론, 비용이 정상인 경우 삽입 할 수 있습니다)
  2. 잠금 획득 [선택 사항] (자문은 테이블 잠금, IMO에 바람직합니다)
  3. 병합. (재미있는 부분)

업데이트는 수정 된 행의 수를 반환합니다. JDBC (Java)를 사용하는 경우 0에 대해이 값을 확인하고 행이 영향을받지 않은 경우 대신 Fire Insert를 확인할 수 있습니다. 다른 프로그래밍 언어를 사용하는 경우 수정 된 행의 수를 여전히 얻을 수 있습니다. 문서를 확인하십시오.

이것은 우아하지는 않지만 호출 코드에서 사용하기에 더 간단한 SQL이 훨씬 간단합니다. 다르게, PL/PSQL에 10 줄 스크립트를 작성하면 단지 하나 또는 다른 종류의 단위 테스트가 있어야 할 것입니다.

편집하다: 이것은 예상대로 작동하지 않습니다. 허용 된 답변과 달리 두 프로세스가 반복적으로 호출 할 때 고유 한 주요 위반이 발생합니다. 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');
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top