2010-08-12 23 views
10

Tôi đang cập nhật cơ sở dữ liệu Postgres 8.4 (từ mã C#) và nhiệm vụ cơ bản là đủ đơn giản: hoặc UPDATE một hàng hiện có hoặc INSERT một hàng mới nếu một doesn ' chưa tồn tại. Thông thường tôi sẽ làm điều này:Postgres UPSERT (INSERT hoặc UPDATE) chỉ khi giá trị khác nhau

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

và nếu 0 hàng bị ảnh hưởng sau đó làm một INSERT:

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

Có một xoắn nhẹ, mặc dù. Tôi không muốn thay đổi các cột updated_timeupdated_username trừ khi bất kỳ giá trị mới nào khác với giá trị hiện tại để tránh gây hiểu lầm cho người dùng về thời điểm dữ liệu được cập nhật.

Nếu tôi chỉ làm một UPDATE thì tôi có thể thêm điều kiện WHERE cho các giá trị, nhưng điều đó sẽ không hoạt động ở đây, bởi vì nếu DB đã được cập nhật thì UPDATE sẽ ảnh hưởng đến 0 hàng và sau đó tôi sẽ cố gắng INSERT.

Có ai có thể nghĩ ra một cách thanh lịch để làm điều này, ngoài SELECT, sau đó là UPDATE hoặc INSERT?

+0

bản sao có thể có của [Chèn, cập nhật trùng lặp (postgresql)] (http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-postgresql) –

+0

Không, không trùng lặp. Câu trả lời ở đó về cơ bản chỉ đóng gói trong một hàm tôi đã viết ở trên. – EMP

Trả lời

-1

Bắt đầu giao dịch. Sử dụng một lựa chọn để xem liệu dữ liệu bạn đã chèn đã tồn tại chưa, nếu có, không làm gì, nếu không cập nhật, nếu nó không tồn tại, sau đó chèn. Cuối cùng đóng giao dịch.

+2

Điều này là tối ưu bởi vì một trường hợp sử dụng phổ biến có thể là chèn hàng loạt các hàng trong một giao dịch –

4

Hãy nhìn vào một UPDATE kích hoạt TRƯỚC để kiểm tra và thiết lập các giá trị chính xác:

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; 
$$; 

Bây giờ bạn thậm chí không cần phải đề cập đến updated_time trường trong truy vấn UPDATE của bạn, nó sẽ được xử lý bởi các cò súng.

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

5

Hai thứ ở đây. Thứ nhất tùy thuộc vào mức độ hoạt động trong cơ sở dữ liệu của bạn, bạn có thể gặp tình trạng cuộc đua giữa việc kiểm tra bản ghi và chèn nó vào một quá trình khác có thể tạo bản ghi đó trong thời gian đó. Hướng dẫn sử dụng chứa ví dụ về cách thực hiện điều này link example

Để tránh thực hiện cập nhật, có thủ tục suppress_redundant_updates_trigger(). Để sử dụng điều này như bạn muốn bạn wold phải có hai trước khi cập nhật kích hoạt đầu tiên sẽ gọi suppress_redundant_updates_trigger() để hủy bỏ bản cập nhật nếu không có thay đổi được thực hiện và thứ hai để thiết lập dấu thời gian và tên người dùng nếu cập nhật được thực hiện. Các trình kích hoạt được kích hoạt theo thứ tự bảng chữ cái. Làm điều này cũng có nghĩa là thay đổi mã trong ví dụ trên để thử chèn trước trước khi cập nhật.

Ví dụ về cách đàn áp cập nhật hoạt động:

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

Mệnh RETURNING phép bạn chuỗi truy vấn của bạn; truy vấn thứ hai sử dụng kết quả từ đầu tiên. (trong trường hợp này để tránh tái chạm vào các hàng tương tự) (RETURNING có sẵn kể từ khi postgres 8.4)

Trên đây nhúng trong một một chức năng, nhưng nó hoạt động cho SQL đơn giản, quá

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 ; 

KẾT QUẢ:

 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

Postgres là nhận được sự ủng hộ UPSERT. Hiện tại, trong số tree kể từ ngày 8 tháng 5 năm 2015 (commit):

Tính năng này thường được gọi là upsert.

Điều này được triển khai bằng cơ sở hạ tầng mới được gọi là "chèn đầu cơ ". Đây là một biến thể lạc quan về chèn thông thường mà trước tiên, thực hiện kiểm tra trước các bộ dữ liệu hiện có và sau đó thử chèn . Nếu một tuple vi phạm được chèn đồng thời, chèn tuple được chèn vào và một lần thử mới được thực hiện. Nếu , việc kiểm tra trước sẽ tìm thấy một bộ tương ứng thay thế KHÔNG LÀM hoặc DO Hành động UPDATE được thực hiện. Nếu việc chèn thành công mà không phát hiện xung đột , thì bộ tuple được coi là được chèn vào.

Ảnh chụp nhanh là available for download. Nó chưa được thực hiện a release.

Các vấn đề liên quan