2010-08-12 2 views
10

C# 코드에서 Postgres 8.4 데이터베이스를 업데이트 중이며 기본 작업은 간단합니다. 기존 행을 UPDATE하거나 INSERT하면 새 행을 삽입합니다. 아직 존재하지 않습니다. 일반적으로 내가 이런 짓을 했을까 :값이 다른 경우에만 Postgres UPSERT (INSERT 또는 UPDATE)

UPDATE my_table 
SET value1 = :newvalue1, ..., updated_time = now(), updated_username = 'evgeny' 
WHERE criteria1 = :criteria1 AND criteria2 = :criteria2 

0 행이 다음 INSERT 할 영향을받은 경우 :하지만,

INSERT INTO my_table(criteria1, criteria2, value1, ...) 
VALUES (:criteria1, :criteria2, :newvalue1, ...) 

약간의 트위스트가입니다. updated_timeupdated_username 열을 변경하고 싶지 않습니다. 데이터를 업데이트 할 때 사용자를 오도하는 것을 방지하기 위해 새 값이 실제로 기존 값과 다른 경우가 있습니다.

만약 내가 단지 UPDATE를하고 있다면, 값을위한 WHERE 조건을 추가 할 수는 있지만, DB가 이미 최신 상태이면 UPDATE가 0 행에 영향을 미칠 것이기 때문에 여기서는 작동하지 않을 것입니다. 삽입을 시도하십시오.

누군가 SELECT를 누른 다음 UPDATE 또는 INSERT 중 하나를 수행하는 우아한 방법을 생각할 수 있습니까?

+0

복제본 [삽입, 중복 업데이트 (postgresql)] 가능 복제본 (http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-postgresql) –

+0

아니요 복제본이 아닙니다. 대답은 기본적으로 위에서 작성한 함수로 캡슐화됩니다. – EMP

답변

-1

트랜잭션을 시작하십시오. select를 사용하여 삽입하려는 데이터가 이미 존재하는지 확인하십시오 (존재하지 않는 경우).없는 경우 그렇지 않으면 갱신하십시오 (존재하지 않는 경우). 마지막으로 트랜잭션을 닫습니다.

+2

일반적인 유스 케이스는 트랜잭션에 대량의 행을 대량 삽입 할 수 있기 때문에 차선책입니다. –

4

확인하고 올바른 값을 설정하는 BEFORE UPDATE 트리거를 살펴 보자 : 이제

CREATE OR REPLACE FUNCTION my_trigger() RETURNS TRIGGER LANGUAGE plpgsql AS 
$$ 
BEGIN 
    IF OLD.content = NEW.content THEN 
     NEW.updated_time= OLD.updated_time; -- use the old value, not a new one. 
    ELSE 
     NEW.updated_time= NOW(); 
    END IF; 
    RETURN NEW; 
END; 
$$; 

당신도 당신의 UPDATE 쿼리에서 필드 updated_time을 언급 할 필요는 없습니다, 그것은에 의해 처리됩니다 방아쇠. 여기

http://www.postgresql.org/docs/current/interactive/plpgsql-trigger.html

5

두 가지. 먼저 데이터베이스의 활동 수준에 따라 레코드를 확인하고 중간에 다른 프로세스가 해당 레코드를 만들 수있는 위치 사이에 경쟁 조건이 발생할 수 있습니다. 설명서는이 link example

가 suppress_redundant_updates_trigger() 프로 시저가 업데이 트를하고 방지하기 위해 수행하는 방법의 예를 들어 있습니다. 업데이트를 시작하기 전에 두 개가 있어야하며, 변경 사항이 없으면 suppress_redundant_updates_trigger()를 호출하여 업데이트를 중단하고 업데이트가 수행되면 타임 스탬프와 사용자 이름을 설정하는 두 번째 호출을 호출합니다. 트리거는 알파벳 순서로 실행됩니다. 이렇게하면 업데이트 전에 삽입을 먼저 시도하도록 위의 예에서 코드를 변경해야합니다. 어떻게 작동하는지 억제 업데이트

예 :

DROP TABLE sru_test; 

    CREATE TABLE sru_test(id integer not null primary key, 
    data text, 
    updated timestamp(3)); 

    CREATE TRIGGER z_min_update 
    BEFORE UPDATE ON sru_test 
    FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); 

    DROP FUNCTION set_updated(); 

    CREATE FUNCTION set_updated() 
    RETURNS TRIGGER 
    AS $$ 
    DECLARE 
    BEGIN 
     NEW.updated := now(); 
     RETURN NEW; 
    END; 
    $$ LANGUAGE plpgsql; 

    CREATE TRIGGER zz_set_updated 
    BEFORE INSERT OR UPDATE ON sru_test 
    FOR EACH ROW EXECUTE PROCEDURE set_updated(); 

insert into sru_test(id,data) VALUES (1,'Data 1'); 
insert into sru_test(id,data) VALUES (2,'Data 2'); 

select * from sru_test; 

update sru_test set data = 'NEW'; 

select * from sru_test; 

update sru_test set data = 'NEW'; 

select * from sru_test; 

update sru_test set data = 'ALTERED' where id = 1; 

select * from sru_test; 

update sru_test set data = 'NEW' where id = 2; 

select * from sru_test; 
0

RETURNING 절은 체인 쿼리 할 수 ​​있습니다; 두 번째 쿼리는 첫 번째 쿼리의 결과를 사용합니다. (이 경우 동일한 행을 다시 만지는 것을 피하기 위해) (RETURNING은 postgres 8부터 가능합니다.4)

여기에 표시되는 기능에 포함하지만, 일반 SQL 작동, 너무

DROP SCHEMA tmp CASCADE; 
CREATE SCHEMA tmp ; 
SET search_path=tmp; 

CREATE TABLE my_table 
     (updated_time timestamp NOT NULL DEFAULT now() 
     , updated_username varchar DEFAULT '_none_' 
     , criteria1 varchar NOT NULL 
     , criteria2 varchar NOT NULL 
     , value1 varchar 
     , value2 varchar 
     , PRIMARY KEY (criteria1,criteria2) 
     ); 

INSERT INTO my_table (criteria1,criteria2,value1,value2) 
SELECT 'C1_' || gs::text 
     , 'C2_' || gs::text 
     , 'V1_' || gs::text 
     , 'V2_' || gs::text 
FROM generate_series(1,10) gs 
     ; 

SELECT * FROM my_table ; 

CREATE function funky(_criteria1 text,_criteria2 text, _newvalue1 text, _newvalue2 text) 
RETURNS VOID 
AS $funk$ 
WITH ins AS (
     INSERT INTO my_table(criteria1, criteria2, value1, value2, updated_username) 
     SELECT $1, $2, $3, $4, COALESCE(current_user, 'evgeny') 
     WHERE NOT EXISTS (
       SELECT * FROM my_table nx 
       WHERE nx.criteria1 = $1 AND nx.criteria2 = $2 
       ) 
     RETURNING criteria1 AS criteria1, criteria2 AS criteria2 
     ) 
     UPDATE my_table upd 
     SET value1 = $3, value2 = $4 
     , updated_time = now() 
     , updated_username = COALESCE(current_user, 'evgeny') 
     WHERE 1=1 
     AND criteria1 = $1 AND criteria2 = $2 -- key-condition 
     AND (value1 <> $3 OR value2 <> $4) -- row must have changed 
     AND NOT EXISTS (
       SELECT * FROM ins -- the result from the INSERT 
       WHERE ins.criteria1 = upd.criteria1 
       AND ins.criteria2 = upd.criteria2 
       ) 
     ; 
$funk$ language sql 
     ; 

SELECT funky('AA', 'BB' , 'CC', 'DD');   -- INSERT 
SELECT funky('C1_3', 'C2_3' , 'V1_3', 'V2_3'); -- (null) UPDATE 
SELECT funky('C1_7', 'C2_7' , 'V1_7', 'V2_7777'); -- (real) UPDATE 

SELECT * FROM my_table ; 

결과 :

 updated_time  | updated_username | criteria1 | criteria2 | value1 | value2 
----------------------------+------------------+-----------+-----------+--------+--------- 
2013-03-13 16:37:55.405267 | _none_   | C1_1  | C2_1  | V1_1 | V2_1 
2013-03-13 16:37:55.405267 | _none_   | C1_2  | C2_2  | V1_2 | V2_2 
2013-03-13 16:37:55.405267 | _none_   | C1_3  | C2_3  | V1_3 | V2_3 
2013-03-13 16:37:55.405267 | _none_   | C1_4  | C2_4  | V1_4 | V2_4 
2013-03-13 16:37:55.405267 | _none_   | C1_5  | C2_5  | V1_5 | V2_5 
2013-03-13 16:37:55.405267 | _none_   | C1_6  | C2_6  | V1_6 | V2_6 
2013-03-13 16:37:55.405267 | _none_   | C1_8  | C2_8  | V1_8 | V2_8 
2013-03-13 16:37:55.405267 | _none_   | C1_9  | C2_9  | V1_9 | V2_9 
2013-03-13 16:37:55.405267 | _none_   | C1_10  | C2_10  | V1_10 | V2_10 
2013-03-13 16:37:55.463651 | postgres   | AA  | BB  | CC  | DD 
2013-03-13 16:37:55.472783 | postgres   | C1_7  | C2_7  | V1_7 | V2_7777 
(11 rows) 
2

포스트 그레스는 UPSERT 지원을 받고있다.

이 기능은 자주 upsert라고합니다 : 그것은 tree 5 월 2015 년 (commit) 8 년 이후 현재.

이것은 "추측 삽입"이라는 새 인프라를 사용하여 구현됩니다. 은 먼저 기존 튜플에 대한 사전 검사를 수행 한 다음 삽입을 시도하는 것이 일반적인 삽입의 낙관적 인 변형입니다. 위반하는 튜플이 동시에 삽입 된 경우, 추측 적으로 삽입 된 튜플 인 이 삭제되고 새로운 시도가 이루어집니다. 사전 검사가 일치하는 튜플을 찾은 경우 다른 DO NOTHING 또는 DO UPDATE 동작이 수행됩니다. 충돌을 감지하지 않고 삽입이 성공하면 튜플이 삽입 된 것으로 간주됩니다.

스냅 샷은 available for download입니다. 아직 a release을 만들지 않았습니다.