Đây là một quyết định thực hiện. Nó được mô tả trong tài liệu Postgres, WITH
Truy vấn (Biểu thức bảng chung) . Có hai đoạn liên quan đến vấn đề.
Đầu tiên, lý do cho hành vi được quan sát:
Các báo cáo phụ WITH
được thực hiện đồng thời với nhau và với truy vấn chính . Do đó, khi sử dụng các câu lệnh sửa đổi dữ liệu WITH
, thứ tự các cập nhật được chỉ định thực sự xảy ra là không thể đoán trước. Tất cả các câu lệnh được thực hiện với cùng một ảnh chụp nhanh (xem Chương 13), vì vậy chúng không thể "nhìn thấy" các hiệu ứng của nhau trên các bảng đích. Điều này làm giảm bớt ảnh hưởng của tính không thể đoán trước của thứ tự cập nhật hàng thực tế và có nghĩa là RETURNING
dữ liệu là cách duy nhất để truyền đạt các thay đổi giữa các WITH
câu lệnh phụ khác nhau và truy vấn chính. Một ví dụ về điều này là trong ...
Sau khi tôi đăng một đề nghị cùng với pssql-docs , Marko Tiikkaja đã giải thích (đồng ý với câu trả lời của Erwin):
Các trường hợp chèn-cập nhật và chèn-xóa không hoạt động vì các CẬP NHẬT và XÓA không có cách nào nhìn thấy các hàng được CHỌN do ảnh chụp nhanh của chúng đã được thực hiện trước khi xảy ra INSERT. Không có gì khó lường về hai trường hợp này.
Vì vậy, lý do tại sao tuyên bố của bạn không cập nhật có thể được giải thích bằng đoạn đầu tiên ở trên (về "ảnh chụp nhanh"). Điều xảy ra khi bạn sửa đổi CTE là tất cả chúng và truy vấn chính được thực thi và "xem" cùng một ảnh chụp nhanh của dữ liệu (bảng), vì chúng ngay lập tức trước khi thực hiện câu lệnh. CTE có thể chuyển thông tin về những gì họ chèn / cập nhật / xóa cho nhau và truy vấn chính bằng cách sử dụng RETURNING
mệnh đề nhưng họ không thể thấy các thay đổi trực tiếp trong các bảng. Vì vậy, hãy xem những gì xảy ra trong tuyên bố của bạn:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
Chúng tôi có 2 phần, CTE ( newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
và truy vấn chính:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
Luồng thực thi là như thế này:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
Kết quả là, khi truy vấn chính kết hợp tbl
(như được thấy trong ảnh chụp nhanh) với newval
bảng, nó sẽ nối một bảng trống với bảng 1 hàng. Rõ ràng nó cập nhật 0 hàng. Vì vậy, tuyên bố không bao giờ thực sự đến để sửa đổi hàng vừa được chèn và đó là những gì bạn thấy.
Giải pháp trong trường hợp của bạn là viết lại câu lệnh để chèn các giá trị đúng vào vị trí đầu tiên hoặc sử dụng 2 câu lệnh. Một trong đó chèn và một giây để cập nhật.
Có những tình huống khác, tương tự, như nếu câu lệnh có một INSERT
và sau đó a DELETE
trên cùng một hàng. Việc xóa sẽ thất bại vì những lý do chính xác như vậy.
Một số trường hợp khác, với cập nhật-cập nhật và cập nhật-xóa và hành vi của chúng được giải thích trong một đoạn sau, trong cùng một trang tài liệu.
Cố gắng cập nhật cùng một hàng hai lần trong một câu lệnh không được hỗ trợ. Chỉ có một trong những sửa đổi diễn ra, nhưng không dễ dàng (và đôi khi không thể) để dự đoán một cách đáng tin cậy cái nào. Điều này cũng áp dụng để xóa một hàng đã được cập nhật trong cùng một tuyên bố: chỉ cập nhật được thực hiện. Do đó, bạn thường nên tránh cố gắng sửa đổi một hàng hai lần trong một câu lệnh. Cụ thể, tránh viết các câu lệnh phụ có thể ảnh hưởng đến cùng một hàng được thay đổi bởi câu lệnh chính hoặc câu lệnh phụ anh chị em. Những ảnh hưởng của một tuyên bố như vậy sẽ không thể dự đoán được.
Và trong thư trả lời từ Marko Tiikkaja:
Các trường hợp cập nhật-cập nhật và cập nhật-xóa không rõ ràng gây ra bởi cùng một chi tiết triển khai cơ bản (như các trường hợp chèn-cập nhật và chèn-xóa).
Trường hợp cập nhật cập nhật không hoạt động vì bên trong nó trông giống như sự cố Halloween và Postgres không có cách nào để biết bộ dữ liệu nào sẽ ổn khi cập nhật hai lần và cái nào có thể giới thiệu lại vấn đề Halloween.
Vì vậy, lý do là như nhau (cách sửa đổi CTE được thực hiện và cách mỗi CTE nhìn thấy cùng một ảnh chụp nhanh) nhưng chi tiết khác nhau trong 2 trường hợp này, vì chúng phức tạp hơn và kết quả có thể không dự đoán được trong trường hợp cập nhật-cập nhật.
Trong bản cập nhật chèn (như trường hợp của bạn) và chèn-xóa tương tự, kết quả có thể dự đoán được. Chỉ có thao tác chèn xảy ra khi thao tác thứ hai (cập nhật hoặc xóa) không có cách nào để xem và ảnh hưởng đến các hàng mới được chèn.
Mặc dù vậy, giải pháp được đề xuất là giống nhau cho tất cả các trường hợp cố gắng sửa đổi cùng một hàng nhiều lần: Đừng làm điều đó. Viết các câu lệnh sửa đổi mỗi hàng một lần hoặc sử dụng các câu lệnh riêng biệt (2 hoặc nhiều hơn).