Làm thế nào để mô phỏng chèn chèn bỏ qua và bỏ qua các bản cập nhật khóa trùng lặp (bản hợp nhất sql) với postgresql?


140

Một số máy chủ SQL có một tính năng INSERTbị bỏ qua nếu nó vi phạm ràng buộc khóa chính / duy nhất. Ví dụ, MySQL có INSERT IGNORE.

Cách tốt nhất để mô phỏng INSERT IGNOREON DUPLICATE KEY UPDATEvới PostgreSQL là gì?




6
kể từ ngày 9.5, điều này hoàn toàn có thể xảy ra: stackoverflow.com/a/34639431/4418
warren

ON DUPLICATE KEY UPDATEViệc mô phỏng MySQL: trên PGQuery 9.5 vẫn còn hơi bất khả thi, vì ON CLAUSEtương đương với PGQuery yêu cầu bạn cung cấp tên ràng buộc, trong khi MySQL có thể nắm bắt bất kỳ ràng buộc nào mà không cần xác định nó. Điều này ngăn tôi "mô phỏng" tính năng này mà không cần viết lại truy vấn.
NeverEinatingQueue

Câu trả lời:


35

Hãy thử làm một CẬP NHẬT. Nếu nó không sửa đổi bất kỳ hàng nào có nghĩa là nó không tồn tại, thì hãy chèn. Rõ ràng, bạn làm điều này trong một giao dịch.

Tất nhiên bạn có thể bọc cái này trong một hàm nếu bạn không muốn đặt thêm mã ở phía máy khách. Bạn cũng cần một vòng lặp cho điều kiện cuộc đua rất hiếm trong suy nghĩ đó.

Có một ví dụ về điều này trong tài liệu: http://www.postgresql.org/docs/9.3/static/plpgsql-control-strucenses.html , ví dụ 40-2 ngay phía dưới.

Đó thường là cách dễ nhất. Bạn có thể làm một số phép thuật với các quy tắc, nhưng nó có thể sẽ phức tạp hơn nhiều. Tôi muốn giới thiệu phương pháp tiếp cận chức năng hơn bất kỳ ngày nào.

Điều này hoạt động cho một hàng, hoặc một vài hàng, giá trị. Nếu bạn đang xử lý một lượng lớn các hàng, ví dụ từ truy vấn phụ, tốt nhất bạn nên chia nó thành hai truy vấn, một cho INSERT và một cho CẬP NHẬT (tất nhiên là tham gia / chọn phụ phù hợp - không cần phải viết chính của bạn lọc hai lần)


4
"Nếu bạn đang xử lý một lượng lớn hàng" đó chính xác là trường hợp của tôi. Tôi muốn cập nhật hàng loạt / chèn hàng và với mysql tôi có thể thực hiện việc này chỉ với MỘT truy vấn mà không cần lặp. Bây giờ tôi tự hỏi nếu điều này là có thể với postgresql: chỉ sử dụng một truy vấn để cập nhật hàng loạt HOẶC chèn. Bạn nói: "tốt nhất bạn nên chia nó thành hai truy vấn, một cho INSERT và một cho CẬP NHẬT" nhưng làm cách nào tôi có thể chèn một lỗi không ném lỗi vào các khóa trùng lặp? (ví dụ: "
XÁC NHẬN

4
Magnus có nghĩa là bạn sử dụng một truy vấn như thế này: "bắt đầu giao dịch; tạo bảng tạm thời tạm thời khi chọn * từ kiểm tra trong đó sai; sao chép tạm thời từ test.id = tạm_table.id; chèn vào kiểm tra chọn * từ tạm thời có thể không có id (chọn id từ kiểm tra) dưới dạng "
Tometzky

25
Cập nhật: với PostgreSQL 9.5, điều này bây giờ đơn giản như INSERT ... ON CONFLICT DO NOTHING;. Xem thêm câu trả lời stackoverflow.com/a/34639431/2091700 .
Alphaaa

Quan trọng, SQL tiêu chuẩn MERGEkhông một đồng thời an toàn upsert, trừ khi bạn chụp LOCK TABLEđầu tiên. Mọi người sử dụng nó theo cách đó, nhưng nó sai.
Craig Ringer

1
Với v9.5 giờ đây là tính năng 'bản địa', vì vậy vui lòng kiểm tra nhận xét của @Alphaaa (chỉ quảng cáo nhận xét quảng cáo câu trả lời)
Camilo Delvasto

178

Với PostgreSQL 9.5, đây là chức năng gốc (như MySQL đã có trong vài năm):

XÁC NHẬN ... TRÊN CONFLICT KHÔNG NÊN / CẬP NHẬT ("UPSERT")

9.5 mang đến sự hỗ trợ cho các hoạt động "UPSERT". INSERT được mở rộng để chấp nhận mệnh đề ON CONFLICT DO UPDATE / IGNORE. Điều khoản này quy định một hành động thay thế sẽ thực hiện trong trường hợp vi phạm sẽ bị trùng lặp.

...

Ví dụ khác về cú pháp mới:

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

100

Chỉnh sửa: trong trường hợp bạn bỏ lỡ câu trả lời của warren , PG9.5 hiện có bản gốc này; Thời gian để nâng cấp!


Dựa trên câu trả lời của Bill Karwin, để đánh vần cách tiếp cận dựa trên quy tắc sẽ như thế nào (chuyển từ một lược đồ khác trong cùng DB và với khóa chính nhiều cột):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

Lưu ý: Quy tắc áp dụng cho tất cả các INSERThoạt động cho đến khi quy tắc được loại bỏ, do đó không hoàn toàn đặc biệt.


@sema bạn có nghĩa là nếu another_schema.my_tablechứa trùng lặp theo các ràng buộc của my_table?
EoghanM

2
@EoghanM Tôi đã thử nghiệm quy tắc trong postgresql 9.3 và vẫn có thể chèn các bản sao với nhiều câu lệnh chèn hàng, ví dụ: INSERT INTO "my_table" (a, b), (a, b); (Giả sử hàng đó (a, b) chưa tồn tại trong "my_table".)
sema

@sema, gotcha - điều đó có nghĩa là quy tắc được thực thi khi bắt đầu tất cả dữ liệu được chèn và không được thực hiện lại sau mỗi hàng được chèn. Một cách tiếp cận sẽ là chèn dữ liệu của bạn vào một bảng tạm thời khác trước mà không có bất kỳ ràng buộc nào và sau đó thực hiệnINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM

@EoghanM Một cách tiếp cận khác là tạm thời nới lỏng các ràng buộc trùng lặp và chấp nhận các bản sao khi chèn, nhưng loại bỏ các bản sao sau đó bằngDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema

Tôi đang gặp vấn đề được mô tả bởi @sema. Nếu tôi thực hiện thao tác chèn (a, b), (a, b), nó sẽ báo lỗi. Có cách nào để triệt tiêu các lỗi không, trong trường hợp này?
Diogo Melo

35

Đối với những bạn có Postgres 9.5 trở lên, cú pháp ON CONFLICT KHÔNG NÊN mới sẽ hoạt động:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

Đối với những người trong chúng ta có phiên bản cũ hơn, tham gia đúng này sẽ hoạt động thay thế:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;

Cách tiếp cận thứ hai không hoạt động khi thực hiện một chèn lớn trong một môi trường đồng thời. Bạn nhận được Unique violation: 7 ERROR: duplicate key value violates unique constraintkhi target_tablecó một hàng khác được chèn vào trong khi truy vấn này đang được thực thi, nếu các khóa của chúng thực sự trùng lặp với nhau. Tôi tin rằng khóa target_tablesẽ giúp ích, nhưng rõ ràng đồng thời sẽ bị ảnh hưởng.
G. Kashtanov

1
ON CONFLICT (field_one) DO NOTHINGlà phần tốt nhất của câu trả lời.
Abel Callejo

24

Để có được logic bỏ qua chèn, bạn có thể làm một cái gì đó như dưới đây. Tôi thấy chỉ đơn giản là chèn từ một câu lệnh chọn các giá trị bằng chữ hoạt động tốt nhất, sau đó bạn có thể che đi các khóa trùng lặp với mệnh đề KHÔNG EXISTS. Để có được bản cập nhật về logic trùng lặp, tôi nghi ngờ một vòng lặp pl / pssql là cần thiết.

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)

Điều gì xảy ra nếu tmp chứa một hàng trùng lặp, điều này có thể xảy ra?
Henley Chiu

Bạn luôn có thể chọn với từ khóa riêng biệt.
Keyo

5
Cũng giống như một FYI, thủ thuật "WHERE NOT EXISTS" không hoạt động trên nhiều giao dịch vì các giao dịch khác nhau có thể thấy dữ liệu mới được thêm từ các giao dịch khác.
Dave Johansen

21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')

Tác động của nhiều giao dịch tất cả cố gắng làm điều tương tự là gì? Có thể là giữa nơi không tồn tại thực thi và chèn thực thi một số giao dịch khác không chèn một hàng? Và nếu Postgres có thể ngăn chặn điều đó, thì không phải Postgres giới thiệu một điểm đồng bộ hóa trên tất cả các giao dịch khi họ đạt được điều này?
αrτhικ

Điều này không hoạt động với nhiều giao dịch, vì dữ liệu mới được thêm vào không thể nhìn thấy đối với các giao dịch khác.
Dave Johansen

12

Có vẻ như PostgreSQL hỗ trợ một đối tượng lược đồ được gọi là quy tắc .

http://www.postgresql.org/docs/civerse/static/rules-update.html

Bạn có thể tạo quy tắc ON INSERTcho một bảng đã cho, làm cho nó thực hiện NOTHINGnếu một hàng tồn tại với giá trị khóa chính đã cho hoặc nếu không thì làm cho nó UPDATEthay vìINSERT nếu một hàng tồn tại với giá trị khóa chính đã cho.

Tôi đã không thử điều này bản thân mình, vì vậy tôi không thể nói từ kinh nghiệm hoặc đưa ra một ví dụ.


1
nếu tôi hiểu rõ các quy tắc này là các kích hoạt được thực thi mỗi khi một câu lệnh được gọi. Điều gì xảy ra nếu tôi muốn áp dụng quy tắc cho chỉ một truy vấn? tôi phải tạo ra quy tắc sau đó ngay lập tức phá hủy nó? (còn điều kiện cuộc đua thì sao?)
gpilotino

3
Vâng, tôi cũng có câu hỏi tương tự. Cơ chế quy tắc là thứ gần nhất mà tôi có thể tìm thấy trong PostgreSQL đối với INSERT IGNORE của MySQL hoặc TRÊN CẬP NHẬT TỪ KHÓA. Nếu chúng tôi google cho "postgresql trên bản cập nhật khóa trùng lặp", bạn sẽ thấy những người khác đề xuất cơ chế Quy tắc, mặc dù Quy tắc sẽ áp dụng cho bất kỳ INSERT nào, không chỉ trên cơ sở ad hoc.
Bill Karwin

4
PostgreSQL hỗ trợ DDL giao dịch, có nghĩa là nếu bạn tạo quy tắc và bỏ quy tắc đó trong một giao dịch, quy tắc sẽ không bao giờ được nhìn thấy bên ngoài (và do đó sẽ không bao giờ có bất kỳ ảnh hưởng nào bên ngoài) giao dịch đó.
cdhowie

6

Như @hanmari đã đề cập trong bình luận của mình. khi chèn vào bảng postgres, xung đột (..) không làm gì là mã tốt nhất để sử dụng để không chèn dữ liệu trùng lặp.:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

Dòng mã ON CONFLICT sẽ cho phép câu lệnh chèn vẫn chèn các hàng dữ liệu. Mã truy vấn và mã giá trị là một ví dụ về ngày được chèn từ Excel vào bảng db postgres. Tôi có các ràng buộc được thêm vào bảng postgres mà tôi sử dụng để đảm bảo trường ID là duy nhất. Thay vì chạy xóa trên các hàng dữ liệu giống nhau, tôi thêm một dòng mã sql để đổi lại cột ID bắt đầu từ 1. Ví dụ:

q = 'ALTER id_column serial RESTART WITH 1'

Nếu dữ liệu của tôi có trường ID, tôi không sử dụng trường này làm ID chính / ID nối tiếp, tôi tạo cột ID và tôi đặt nó thành nối tiếp. Tôi hy vọng thông tin này hữu ích cho mọi người. * Tôi không có bằng đại học về phát triển / mã hóa phần mềm. Tất cả mọi thứ tôi biết về mã hóa, tôi tự học.


điều này không hoạt động trên các chỉ số duy nhất tổng hợp!
Nulik

4

Giải pháp này tránh sử dụng các quy tắc:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

nhưng nó có một nhược điểm về hiệu năng (xem PostgreSQL.org ):

Một khối chứa mệnh đề EXCEPTION đắt hơn đáng kể để nhập và thoát so với khối không có. Do đó, không sử dụng NGOẠI TRỪ mà không cần.


1

Trên số lượng lớn, bạn luôn có thể xóa hàng trước khi chèn. Việc xóa một hàng không tồn tại không gây ra lỗi, vì vậy nó bị bỏ qua một cách an toàn.


2
Cách tiếp cận này sẽ khá thiên về các điều kiện chủng tộc kỳ lạ, tôi sẽ không đề xuất nó ...
Steven Schlansker

1
+1 Điều này dễ dàng và chung chung. Nếu được sử dụng cẩn thận, đây thực sự có thể là một giải pháp đơn giản.
Wouter van Nifterick

1
Nó cũng sẽ không hoạt động khi dữ liệu hiện tại đã bị thay đổi sau khi chèn (nhưng không phải trên khóa trùng lặp) và chúng tôi muốn giữ các bản cập nhật. Đây là kịch bản khi có các tập lệnh SQL được viết cho một số hệ thống hơi khác nhau, như các bản cập nhật db chạy trên các hệ thống sản xuất, QA, dev và thử nghiệm.
Hanno Fietz

1
Khóa ngoại có thể không có vấn đề gì nếu bạn tạo chúng bằng DEFERRABLE INITIALLY DEFERREDcờ.
temoto

-1

Đối với các tập lệnh nhập dữ liệu, để thay thế "NẾU KHÔNG EXISTS", theo một cách nào đó, có một công thức hơi khó xử mà vẫn hoạt động:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
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.