UPDATE
cú pháp yêu cầu để đặt tên rõ ràng cho các cột mục tiêu. Các lý do có thể để tránh điều đó:
- Bạn có nhiều cột và chỉ muốn rút ngắn cú pháp.
- Bạn không biết tên cột ngoại trừ (các) cột duy nhất.
"All columns"
có nghĩa là "tất cả các cột của bảng mục tiêu" (hoặc ít nhất là "các cột đầu bảng" ) theo thứ tự khớp và kiểu dữ liệu khớp. Nếu không, bạn vẫn phải cung cấp danh sách tên cột mục tiêu.
Bảng kiểm tra:
CREATE TABLE tbl (
id int PRIMARY KEY
, text text
, extra text
);
INSERT INTO tbl AS t
VALUES (1, 'foo')
, (2, 'bar');
1. DELETE
&INSERT
thay vào đó trong một truy vấn duy nhất
Không biết bất kỳ tên cột nào ngoại trừ id
.
Chỉ hoạt động cho "tất cả các cột của bảng mục tiêu" . Trong khi cú pháp thậm chí hoạt động cho một tập hợp con đứng đầu, các cột thừa trong bảng đích sẽ được đặt lại thành NULL với DELETE
và INSERT
.
UPSERT (INSERT ... ON CONFLICT ...
) là cần thiết để tránh các vấn đề đồng thời / khóa khi tải ghi đồng thời và chỉ vì không có cách chung nào để khóa các hàng chưa tồn tại trong Postgres ( khóa giá trị ).
Yêu cầu đặc biệt của bạn chỉ ảnh hưởng đến UPDATE
phần. Các biến chứng có thể xảy ra không áp dụng ở những nơi hiện có hàng bị ảnh hưởng. Chúng đã được khóa đúng cách. Đơn giản hóa một số chi tiết, bạn có thể giảm trường hợp của mình thành DELETE
và INSERT
:
WITH data(id) AS ( -- Only 1st column gets explicit name!
VALUES
(1, 'foo_upd', 'a') -- changed
, (2, 'bar', 'b') -- unchanged
, (3, 'baz', 'c') -- new
)
, del AS (
DELETE FROM tbl AS t
USING data d
WHERE t.id = d.id
-- AND t <> d -- optional, to avoid empty updates
) -- only works for complete rows
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO NOTHING
RETURNING t.id;
Trong mô hình Postgres MVCC, một UPDATE
phần lớn giống với DELETE
và INSERT
dù sao (ngoại trừ một số trường hợp góc có đồng thời, cập nhật HOT và giá trị cột lớn được lưu trữ ngoài dòng). Vì bạn vẫn muốn thay thế tất cả các hàng, chỉ cần xóa các hàng xung đột trước INSERT
. Các hàng đã xóa vẫn bị khóa cho đến khi giao dịch được cam kết. INSERT
chỉ có thể tìm thấy các hàng xung đột cho các giá trị khóa không tồn tại trước đó nếu một giao dịch đồng thời xảy ra chèn chúng đồng thời (sau DELETE
, nhưng trước INSERT
).
Bạn sẽ mất các giá trị cột bổ sung cho các hàng bị ảnh hưởng trong trường hợp đặc biệt này. Không có ngoại lệ nào được nêu ra. Nhưng nếu các truy vấn cạnh tranh có mức độ ưu tiên ngang nhau, thì đó hầu như không phải là vấn đề:truy vấn khác giành chiến thắng cho một số hàng. Ngoài ra, nếu truy vấn khác là một UPSERT tương tự, cách thay thế của nó là đợi giao dịch này cam kết và sau đó cập nhật ngay lập tức. "Chiến thắng" có thể là một chiến thắng kiểu Pyrrhic.
Giới thiệu về "bản cập nhật trống":
- Làm cách nào để (hoặc tôi có thể) CHỌN DISTINCT trên nhiều cột?
Không, truy vấn của tôi phải thắng!
OK, bạn đã yêu cầu nó:
WITH data(id) AS ( -- Only 1st column gets explicit name!
VALUES -- rest gets default names "column2", etc.
(1, 'foo_upd', NULL) -- changed
, (2, 'bar', NULL) -- unchanged
, (3, 'baz', NULL) -- new
, (4, 'baz', NULL) -- new
)
, ups AS (
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO UPDATE
SET id = t.id
WHERE false -- never executed, but locks the row!
RETURNING t.id
)
, del AS (
DELETE FROM tbl AS t
USING data d
LEFT JOIN ups u USING (id)
WHERE u.id IS NULL -- not inserted !
AND t.id = d.id
-- AND t <> d -- avoid empty updates - only for full rows
RETURNING t.id
)
, ins AS (
INSERT INTO tbl AS t
SELECT *
FROM data
JOIN del USING (id) -- conflict impossible!
RETURNING id
)
SELECT ARRAY(TABLE ups) AS inserted -- with UPSERT
, ARRAY(TABLE ins) AS updated -- with DELETE & INSERT;
Làm thế nào?
- Dữ liệu CTE
data
đầu tiên chỉ cung cấp dữ liệu. Thay vào đó, có thể là một cái bàn. - CTE thứ 2
ups
:UPSERT. Các hàng cóid
xung đột không bị thay đổi nhưng cũng bị khóa . - CTE thứ 3
del
xóa các hàng xung đột. Chúng vẫn bị khóa. - CTE thứ 4
ins
chèn toàn bộ hàng . Chỉ được phép cho cùng một giao dịch - Lựa chọn cuối cùng chỉ dành cho bản demo để hiển thị những gì đã xảy ra.
Để kiểm tra các bản cập nhật trống, hãy thử nghiệm (trước và sau) với:
SELECT ctid, * FROM tbl; -- did the ctid change?
Kiểm tra (đã nhận xét) xem có bất kỳ thay đổi nào trong hàng AND t <> d
hoạt động ngay cả với các giá trị NULL vì chúng tôi đang so sánh hai giá trị hàng đã nhập theo hướng dẫn sử dụng:
hai giá trị trường NULL được coi là bằng nhau và giá trị NULL được coi là lớn hơn giá trị không NULL
2. SQL động
Điều này cũng hoạt động cho một tập hợp con các cột hàng đầu, bảo toàn các giá trị hiện có.
Bí quyết là cho phép Postgres xây dựng chuỗi truy vấn với các tên cột từ danh mục hệ thống và sau đó thực thi nó.
Xem các câu trả lời liên quan cho mã:
-
Cập nhật nhiều cột trong một hàm kích hoạt trong plpgsql
-
Cập nhật hàng loạt tất cả các cột
-
Các trường cập nhật SQL của một bảng từ các trường của một bảng khác