Sử dụng nhiều mục tiêu xung đột trong mệnh đề ON CONFLICT


93

Tôi có hai cột trong bảng col1, col2cả hai đều được lập chỉ mục duy nhất (col1 là duy nhất và col2 cũng vậy).

Tôi cần chèn vào bảng này, sử dụng ON CONFLICTcú pháp và cập nhật các cột khác, nhưng tôi không thể sử dụng cả hai cột trong conflict_targetmệnh đề.

Nó hoạt động:

INSERT INTO table
...
ON CONFLICT ( col1 ) 
DO UPDATE 
SET 
-- update needed columns here

Nhưng làm thế nào để thực hiện điều này cho một số cột, giống như sau:

...
ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 
....

4
"col1, col2, cả hai đều được lập chỉ mục duy nhất." điều đó có nghĩa là col1 là duy nhất và col2 là duy nhất hay là sự kết hợp của col1, col2 là duy nhất?
e4c5

1
làm thế col1 trung bình là duy nhất và col2 là độc đáo, riêng biệt
Oto Shavadze

Câu trả lời:


48

Một bảng mẫu và dữ liệu

CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
   CONSTRAINT col2_unique UNIQUE (col2)
);

INSERT INTO dupes values(1,1,'a'),(2,2,'b');

Tái tạo vấn đề

INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2

Hãy gọi đây là Q1. Kết quả là

ERROR:  duplicate key value violates unique constraint "col2_unique"
DETAIL:  Key (col2)=(2) already exists.

Những gì các tài liệu nói

Xung đột_target có thể thực hiện suy luận chỉ mục duy nhất. Khi thực hiện suy luận, nó bao gồm một hoặc nhiều cột index_column_name và / hoặc biểu thức index_expression và một index_predicate tùy chọn. Tất cả các chỉ mục duy nhất của tên_bảng, không liên quan đến thứ tự, chứa chính xác các cột / biểu thức được chỉ định bởi mục tiêu xung đột được suy ra (được chọn) làm chỉ mục trọng tài. Nếu một index_predicate được chỉ định, nó phải đáp ứng các chỉ mục trọng tài như một yêu cầu khác để suy luận.

Điều này tạo ấn tượng rằng truy vấn sau sẽ hoạt động, nhưng nó không hoạt động vì nó thực sự yêu cầu một chỉ mục duy nhất cùng nhau trên col1 và col2. Tuy nhiên một chỉ số như vậy sẽ không đảm bảo rằng col1 và col2 sẽ là duy nhất riêng lẻ, đây là một trong những yêu cầu của OP.

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2

Hãy gọi truy vấn này là Q2 (không thành công do lỗi cú pháp)

Tại sao?

Postgresql hoạt động theo cách này là do điều gì sẽ xảy ra khi xung đột xảy ra trên cột thứ hai không được xác định rõ. Có một số khả năng. Ví dụ trong truy vấn Q1 ở trên, postgresql có nên cập nhật col1khi có xung đột trên col2không? Nhưng nếu điều đó dẫn đến một cuộc xung đột khác col1thì sao? postgresql dự kiến ​​sẽ xử lý điều đó như thế nào?

Một giải pháp

Một giải pháp là kết hợp ON CONFLICT với UPSERT kiểu cũ .

CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
        IF found THEN
            RETURN;
        END IF;

        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently, or key2
        -- already exists in col2,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            BEGIN
                INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
                RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- Do nothing, and loop to try the UPDATE again.
            END;
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

Bạn cần phải sửa đổi logic của hàm được lưu trữ này để nó cập nhật các cột chính xác theo cách bạn muốn. Gọi nó như

SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');

3
Đây là cách hoạt động nhưng nhiều công việc / logic hơn mức cần thiết, tất cả những gì bạn phải làm thực sự là tạo một ràng buộc duy nhất trên hai cột. Xem câu trả lời của tôi bên dưới.
Jubair

tôi có thể sử dụng giải pháp merge_db nếu tôi đang chèn nhiều bộ GIÁ TRỊ cùng một lúc không?
daniyel

@daniyel bạn sẽ phải viết lại được lưu trữ chức năng
e4c5

3
Tôi không rõ việc đề xuất sử dụng upsert kiểu cũ sẽ hữu ích như thế nào - câu hỏi này được tham khảo tốt cho "postgres upsert 9.5" và có thể tốt hơn bằng cách giải thích cách sử dụng nó với tất cả các tùy chọn bind_names.
Pak

3
@Pak Bạn không rõ vì bạn chưa đọc rõ câu hỏi. Op không tìm kiếm một khóa tổng hợp trên các trường đó. Câu trả lời khác hoạt động cho các khóa tổng hợp
e4c5

65

ON CONFLICTyêu cầu một chỉ mục duy nhất * để phát hiện xung đột. Vì vậy, bạn chỉ cần tạo một chỉ mục duy nhất trên cả hai cột:

t=# create table t (id integer, a text, b text);
CREATE TABLE
t=# create unique index idx_t_id_a on t (id, a);
CREATE INDEX
t=# insert into t values (1, 'a', 'foo');
INSERT 0 1
t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar';
INSERT 0 1
t=# select * from t;
 id | a |  b  
----+---+-----
  1 | a | bar

* Ngoài các chỉ mục duy nhất, bạn cũng có thể sử dụng các ràng buộc loại trừ . Đây là những ràng buộc chung hơn một chút so với các ràng buộc duy nhất. Giả sử bảng của bạn có các cột cho idvalid_time(và valid_timelà a tsrange), và bạn muốn cho phép các cột trùng lặp id, nhưng không cho phép các khoảng thời gian trùng lặp. Một ràng buộc duy nhất sẽ không giúp được gì cho bạn, nhưng với một ràng buộc loại trừ, bạn có thể nói "loại trừ các bản ghi mới nếu chúng idbằng bản cũ idvà cũng valid_timetrùng lặp với nó valid_time."


4
Điều này tạo ra là một chỉ mục duy nhất cùng nhau tạo chỉ mục duy nhất idx_t_id_a trên t (id, a); Tất nhiên OP không nói rõ hai cột là duy nhất riêng lẻ hay cùng nhau.
e4c5

Tại sao đôi khi postgres nói rằng không có cột nào được đặt tên theo chỉ mục và không sử dụng được ON CONFLICT?
Pak

@Pak, có vẻ như bạn nên viết câu hỏi của riêng mình bằng lệnh cụ thể mà bạn đang sử dụng và thông báo lỗi bạn nhận được.
Paul A Jungwirth

@PaulAJungwirth Tôi không biết, câu trả lời của bạn là đúng - một chỉ mục duy nhất làm ràng buộc cho on conflictlệnh. Lỗi chỉ là "cột my_index_name không tồn tại".
Pak

Tôi đã thử điều này bằng cách nào đó với một ràng buộc duy nhất riêng biệt trên mỗi cột như OP đang yêu cầu, và nó không hoạt động. Không phải tôi mong đợi, nhưng tôi đã hy vọng.
sudo

5

Trong ngày nay là (dường như) không thể. Cả phiên bản cuối cùng của ON CONFLICT cú pháp đều không cho phép lặp lại mệnh đề, hoặc với CTE cũng không được: không thể ngắt INSERT từ ON CONFLICT để thêm nhiều mục tiêu xung đột hơn.


3

Nếu bạn đang sử dụng postgres 9.5, bạn có thể sử dụng không gian LOẠI TRỪ.

Ví dụ được lấy từ Có gì mới trong PostgreSQL 9.5 :

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1)
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;

2
  1. Tạo một ràng buộc (ví dụ: chỉ mục nước ngoài).

HOẶC VÀ

  1. Xem xét các ràng buộc hiện có (\ d trong psq).
  2. Sử dụng ON CONSTRAINT (tên_mạch_mạch) trong mệnh đề INSERT.

1

Vlad có ý tưởng đúng.

Đầu tiên, bạn phải tạo một ràng buộc duy nhất của bảng trên các cột col1, col2 Sau đó, khi bạn làm điều đó, bạn có thể làm như sau:

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT ON CONSTRAINT dupes_pkey 
DO UPDATE SET col3 = 'c', col2 = 2

4
Xin lỗi, nhưng bạn đã hiểu sai câu hỏi. OP không muốn có một ràng buộc duy nhất cùng nhau.
e4c5

1

Hơi khó nhưng tôi đã giải quyết điều này bằng cách nối hai giá trị từ col1 và col2 vào một cột mới, col3 (giống như một chỉ số của cả hai) và so sánh với điều đó. Điều này chỉ hoạt động nếu bạn cần nó khớp CẢ HAI col1 và col2.

INSERT INTO table
...
ON CONFLICT ( col3 ) 
DO UPDATE 
SET 
-- update needed columns here

Trong đó col3 = nối các giá trị từ col1 và col2.


3
bạn có thể tạo một chỉ mục duy nhất cho hai cột đó và đưa ra ràng buộc đó on conflict.
Kishore Relangi

0

Thông thường, bạn có thể (tôi nghĩ) tạo ra một câu lệnh chỉ có một câu lệnh on conflictxác định ràng buộc một và duy nhất có liên quan, cho thứ bạn đang chèn.

Bởi vì thông thường, tại một thời điểm chỉ có một ràng buộc là "có liên quan". (Nếu nhiều, thì tôi đang tự hỏi liệu có thứ gì đó kỳ lạ / được thiết kế kỳ quặc không, hmm.)

Ví dụ:
(Giấy phép: Không phải CC0, chỉ CC-By)

// there're these unique constraints:
//   unique (site_id, people_id, page_id)
//   unique (site_id, people_id, pages_in_whole_site)
//   unique (site_id, people_id, pages_in_category_id)
// and only *one* of page-id, category-id, whole-site-true/false
// can be specified. So only one constraint is "active", at a time.

val thingColumnName = thingColumnName(notfificationPreference)

val insertStatement = s"""
  insert into page_notf_prefs (
    site_id,
    people_id,
    notf_level,
    page_id,
    pages_in_whole_site,
    pages_in_category_id)
  values (?, ?, ?, ?, ?, ?)
  -- There can be only one on-conflict clause.
  on conflict (site_id, people_id, $thingColumnName)   <—— look
  do update set
    notf_level = excluded.notf_level
  """

val values = List(
  siteId.asAnyRef,
  notfPref.peopleId.asAnyRef,
  notfPref.notfLevel.toInt.asAnyRef,
  // Only one of these is non-null:
  notfPref.pageId.orNullVarchar,
  if (notfPref.wholeSite) true.asAnyRef else NullBoolean,
  notfPref.pagesInCategoryId.orNullInt)

runUpdateSingleRow(insertStatement, values)

Và:

private def thingColumnName(notfPref: PageNotfPref): String =
  if (notfPref.pageId.isDefined)
    "page_id"
  else if (notfPref.pagesInCategoryId.isDefined)
    "pages_in_category_id"
  else if (notfPref.wholeSite)
    "pages_in_whole_site"
  else
    die("TyE2ABK057")

Các on conflictkhoản được tạo động, tùy thuộc vào những gì tôi đang cố gắng làm. Nếu tôi đang chèn tùy chọn thông báo, cho một trang - thì có thể có một xung đột duy nhất, về site_id, people_id, page_idràng buộc. Và nếu tôi đang định cấu hình prefs thông báo, cho một danh mục - thì thay vào đó tôi biết rằng ràng buộc có thể bị vi phạm site_id, people_id, category_id.

Vì vậy, tôi có thể, và khá có khả năng bạn cũng vậy, trong trường hợp của bạn ?, tạo ra đúng on conflict (... columns ), bởi vì tôi biết những gì tôi muốn làm, và sau đó tôi biết một trong nhiều ràng buộc duy nhất, là ràng buộc có thể bị vi phạm.


-4

ON CONFLICT là giải pháp rất vụng về, chạy

UPDATE dupes SET key1=$1, key2=$2 where key3=$3    
if rowcount > 0    
  INSERT dupes (key1, key2, key3) values ($1,$2,$3);

hoạt động trên Oracle, Postgres và tất cả các cơ sở dữ liệu khác


Nó không phải là nguyên tử, vì vậy nó có thể bị lỗi và tạo ra kết quả sai trong trường hợp có nhiều kết nối cùng một lúc.
Bogdan Mart
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.