Lỗi bạn gặp phải:
Lệnh ON CONFLICT DO UPDATE không thể ảnh hưởng đến hàng lần thứ hai
cho biết bạn đang cố gắng nâng cấp cùng một hàng nhiều lần trong một lệnh. Nói cách khác:bạn có lỗi trên (name, url, email)
trong VALUES
của bạn danh sách. Gấp các bản sao (nếu đó là một tùy chọn) và nó sẽ hoạt động. Nhưng bạn sẽ phải quyết định chọn hàng nào từ mỗi tập hợp các lỗi.
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
('blah', 'blah', 'blah', 'blah', 'blah')
-- ... more
) v(created, modified, name, url, email) -- match column list
ON CONFLICT (name, url, email) DO UPDATE
SET url = feeds_person.url
RETURNING id;
Vì chúng tôi sử dụng VALUES
độc lập bây giờ, bạn phải thêm các phôi kiểu rõ ràng cho các kiểu không mặc định. Như:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
timestamptz
của bạn các cột cần truyền kiểu rõ ràng, trong khi các loại chuỗi có thể hoạt động với text
mặc định . (Bạn vẫn có thể truyền tới varchar(n)
ngay lập tức.)
Có nhiều cách để xác định hàng nào cần chọn từ mỗi tập hợp các lỗi:
- Chọn hàng đầu tiên trong mỗi GROUP BY nhóm?
Bạn nói đúng, (hiện tại) không có cách nào để bị loại trừ các hàng trong RETURNING
mệnh đề. Tôi trích dẫn Wiki của Postgres:
Lưu ý rằng
RETURNING
không hiển thị "EXCLUDED.*
"bí danh từUPDATE
(chỉ là "TARGET.*
chung chung "bí danh có thể nhìn thấy ở đây). Làm như vậy được cho là tạo ra sự mơ hồ khó chịu cho các trường hợp đơn giản, phổ biến [30] vì ít hoặc không có lợi. Tại một số thời điểm trong tương lai, chúng tôi có thể theo đuổi một cách hiển thị nếuRETURNING
-các bộ giá trị dự án đã được chèn và cập nhật, nhưng điều này chắc chắn không cần phải đưa nó vào lần lặp cam kết đầu tiên của tính năng [31].
Tuy nhiên , bạn không nên cập nhật các hàng không được cập nhật. Các bản cập nhật trống gần như đắt tiền như các bản cập nhật thông thường - và có thể có các tác dụng phụ không mong muốn. Bạn không hoàn toàn cần UPSERT để bắt đầu, trường hợp của bạn trông giống như "CHỌN hoặc CHÈN". Có liên quan:
- CHỌN hoặc CHÈN trong một hàm có nguy cơ gặp phải các điều kiện về chủng tộc không?
Một cách tốt hơn để chèn một tập hợp các hàng sẽ là với CTE sửa đổi dữ liệu:
WITH val AS (
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
(timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
, ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
-- more (type cast only needed in 1st row)
) v(created, modified, name, url, email)
)
, ins AS (
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT created, modified, name, url, email FROM val
ON CONFLICT (name, url, email) DO NOTHING
RETURNING id, name, url, email
)
SELECT 'inserted' AS how, id FROM ins -- inserted
UNION ALL
SELECT 'selected' AS how, f.id -- not inserted
FROM val v
JOIN feeds_person f USING (name, url, email);
Sự phức tạp tăng thêm sẽ phải trả cho các bảng lớn trong đó INSERT
là quy tắc và SELECT
ngoại lệ.
Ban đầu, tôi đã thêm một NOT EXISTS
vị từ trên SELECT
cuối cùng để ngăn chặn các bản sao trong kết quả. Nhưng điều đó là thừa. Tất cả các CTE của một truy vấn đều nhìn thấy cùng một ảnh chụp nhanh của các bảng. Tập hợp được trả về với ON CONFLICT (name, url, email) DO NOTHING
loại trừ lẫn nhau đối với tập hợp được trả về sau INNER JOIN
trên cùng các cột.
Thật không may, điều này cũng mở ra một cửa sổ nhỏ cho điều kiện cuộc đua . Nếu ...
- một giao dịch đồng thời sẽ chèn các hàng xung đột
- chưa cam kết
- nhưng cuối cùng vẫn cam kết
... một số hàng có thể bị mất.
Bạn có thể chỉ cần INSERT .. ON CONFLICT DO NOTHING
, theo sau là SELECT
riêng biệt truy vấn cho tất cả các hàng - trong cùng một giao dịch để khắc phục điều này. Lần lượt mở ra một cửa sổ nhỏ khác cho điều kiện cuộc đua nếu các giao dịch đồng thời có thể cam kết ghi vào bảng giữa INSERT
và SELECT
(ở mặc định READ COMMITTED
mặc định mức cô lập). Có thể tránh được bằng REPEATABLE READ
cách ly giao dịch (hoặc chặt chẽ hơn). Hoặc với một khóa ghi (có thể đắt tiền hoặc thậm chí không thể chấp nhận được) trên toàn bộ bảng. Bạn có thể thực hiện bất kỳ hành vi nào bạn cần, nhưng có thể có một cái giá phải trả.
Có liên quan:
- Làm thế nào để sử dụng RETURNING với ON CONFLICT trong PostgreSQL?
- Trả lại các hàng từ INSERT với ON CONFLICT mà không cần cập nhật