Postgres 9.5 được triển khai UPSERT
. Xem bên dưới.
Postgres 9.4 trở lên
Đây là một vấn đề khó. Bạn đang gặp phải hạn chế này (theo tài liệu):
Trong một
VALUES
danh sách xuất hiện ở cấp cao nhất củaINSERT
, có thể thay thế biểu thức khác bằngDEFAULT
để chỉ ra rằng giá trị mặc định của cột đích phải được chèn.DEFAULT
không thể được sử dụng khiVALUES
xuất hiện trong các ngữ cảnh khác.
Tôi nhấn mạnh đậm. Giá trị mặc định không được xác định nếu không có bảng để chèn vào. Vì vậy, không có trực tiếp giải pháp cho câu hỏi của bạn, nhưng có một số tuyến đường thay thế có thể xảy ra, tùy thuộc vào yêu cầu chính xác .
Tìm nạp các giá trị mặc định từ danh mục hệ thống?
Bạn có thể tìm nạp chúng từ danh mục hệ thống pg_attrdef
như @Patrick đã nhận xét hoặc từ information_schema.columns
. Toàn bộ hướng dẫn tại đây:
- Nhận các giá trị mặc định của các cột trong bảng trong Postgres?
Nhưng sau đó bạn vẫn vẫn chỉ có danh sách hàng với một đại diện văn bản của biểu thức để nấu giá trị mặc định. Bạn sẽ phải xây dựng và thực thi các câu lệnh một cách linh hoạt để nhận các giá trị để làm việc với. Tẻ nhạt và lộn xộn. Thay vào đó, chúng tôi có thể để chức năng Postgres tích hợp sẵn làm điều đó cho chúng tôi :
Phím tắt đơn giản
Chèn một hàng giả và đặt hàng đó trở lại để sử dụng các giá trị mặc định đã tạo:
INSERT INTO playlist_items DEFAULT VALUES RETURNING *;
Vấn đề / phạm vi của giải pháp
- Điều này chỉ được đảm bảo hoạt động cho
STABLE
hoặcIMMUTABLE
biểu thức mặc định . Hầu hếtVOLATILE
các chức năng sẽ hoạt động tốt, nhưng không có gì đảm bảo.current_timestamp
nhóm hàm đủ điều kiện là ổn định, vì giá trị của chúng không thay đổi trong một giao dịch.
Đặc biệt, điều này có tác dụng phụ đối vớiserial
cột (hoặc bất kỳ bản vẽ mặc định nào khác từ một chuỗi). Nhưng đó không phải là vấn đề, vì bạn thường không ghi vàoserial
các cột trực tiếp. Những thứ đó không nên được liệt kê trongINSERT
tất cả các câu lệnh.
Lỗ hổng còn lại choserial
cột:trình tự vẫn được nâng cao bởi một lệnh gọi để lấy một hàng mặc định, tạo ra một khoảng trống trong việc đánh số. Một lần nữa, đó không phải là một vấn đề, bởi vì những khoảng trống thường được mong đợi trongserial
cột.
Hai vấn đề khác có thể được giải quyết:
-
Nếu bạn có các cột được xác định
NOT NULL
, bạn phải chèn các giá trị giả và thay thế bằngNULL
trong kết quả. -
Chúng tôi thực sự không muốn chèn hàng giả . Chúng tôi có thể xóa sau (trong cùng một giao dịch), nhưng điều đó có thể có nhiều tác dụng phụ hơn, chẳng hạn như trình kích hoạt
ON DELETE
. Có một cách tốt hơn:
Tránh hàng giả
Sao chép bảng tạm thời bao gồm các giá trị mặc định của cột và chèn vào that :
BEGIN;
CREATE TEMP TABLE tmp_playlist_items (LIKE playlist_items INCLUDING DEFAULTS)
ON COMMIT DROP; -- drop at end of transaction
INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *;
...
Kết quả tương tự, ít tác dụng phụ hơn. Vì các biểu thức mặc định được sao chép nguyên văn, bản sao sẽ lấy từ các trình tự giống nhau nếu có. Nhưng hoàn toàn tránh được các tác dụng phụ khác từ hàng hoặc tác nhân kích hoạt không mong muốn.
Ghi công cho Igor cho ý tưởng:
- Postgresql, chọn một hàng "giả"
Xóa NOT NULL
ràng buộc
Bạn sẽ phải cung cấp các giá trị giả cho NOT NULL
cột, bởi vì (theo tài liệu):
Các ràng buộc not-null luôn được sao chép vào bảng mới.
Phù hợp với những người trong INSERT
tuyên bố hoặc (tốt hơn) loại bỏ các ràng buộc:
ALTER TABLE tmp_playlist_items
ALTER COLUMN foo DROP NOT NULL
, ALTER COLUMN bar DROP NOT NULL;
Có một cách nhanh chóng và bẩn thỉu với các đặc quyền của người dùng siêu cấp:
UPDATE pg_attribute
SET attnotnull = FALSE
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0;
Nó chỉ là một bảng tạm thời không có dữ liệu và không có mục đích nào khác, và nó sẽ bị xóa khi kết thúc giao dịch. Vì vậy, đường tắt là hấp dẫn. Tuy nhiên, quy tắc cơ bản là:không bao giờ can thiệp trực tiếp vào danh mục hệ thống.
Vì vậy, hãy xem xét một cách sạch sẽ :Tự động hóa với SQL động trong DO
tuyên bố. Bạn chỉ cần có đặc quyền thông thường bạn được đảm bảo có cùng một vai trò đã tạo bảng tạm thời.
DO $$BEGIN
EXECUTE (
SELECT 'ALTER TABLE tmp_playlist_items ALTER '
|| string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
|| ' DROP NOT NULL'
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0
);
END$$
Gọn gàng hơn nhiều và vẫn rất nhanh. Thực thi cẩn thận với các lệnh động và cảnh giác với SQL injection. Tuyên bố này là an toàn. Tôi đã đăng một số câu trả lời liên quan với nhiều lời giải thích hơn.
Giải pháp chung (9.4 trở lên)
BEGIN;
CREATE TEMP TABLE tmp_playlist_items
(LIKE playlist_items INCLUDING DEFAULTS) ON COMMIT DROP;
DO $$BEGIN
EXECUTE (
SELECT 'ALTER TABLE tmp_playlist_items ALTER '
|| string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
|| ' DROP NOT NULL'
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0
);
END$$;
LOCK TABLE playlist_items IN EXCLUSIVE MODE; -- forbid concurrent writes
WITH default_row AS (
INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *
)
, new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
VALUES
(651, 21, 30012, 'a', 30, 1, FALSE)
, (NULL, 21, 1, 'b', 34, 2, NULL)
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
)
, upsert AS ( -- *not* replacing existing values in UPDATE (?)
UPDATE playlist_items m
SET ( playlist, item, group_name, duration, sort, legacy)
= (n.playlist, n.item, n.group_name, n.duration, n.sort, n.legacy)
-- ..., COALESCE(n.legacy, m.legacy) -- see below
FROM new_values n
WHERE n.id = m.id
RETURNING m.id
)
INSERT INTO playlist_items
(playlist, item, group_name, duration, sort, legacy)
SELECT n.playlist, n.item, n.group_name, n.duration, n.sort
, COALESCE(n.legacy, d.legacy)
FROM new_values n, default_row d -- single row can be cross-joined
WHERE NOT EXISTS (SELECT 1 FROM upsert u WHERE u.id = n.id)
RETURNING id;
COMMIT;
Bạn chỉ cần LOCK
nếu bạn có các giao dịch đồng thời đang cố gắng ghi vào cùng một bảng.
Theo yêu cầu, điều này chỉ thay thế các giá trị NULL trong cột legacy
trong các hàng đầu vào cho INSERT
trường hợp. Có thể dễ dàng mở rộng để làm việc cho các cột khác hoặc trong UPDATE
cả trường hợp nữa. Ví dụ:bạn có thể UPDATE
cũng như có điều kiện:chỉ khi giá trị đầu vào là NOT NULL
. Tôi đã thêm một dòng nhận xét vào UPDATE
ở trên.
Ngoài ra:Bạn không cần phải cast giá trị trong bất kỳ hàng nào trừ giá trị đầu tiên trong VALUES
biểu thức, vì các loại được bắt nguồn từ đầu tiên hàng.
Postgres 9.5
triển khai UPSERT with INSERT .. ON CONFLICT .. DO NOTHING | UPDATE
. Điều này phần lớn đơn giản hóa hoạt động:
INSERT INTO playlist_items AS m (id, playlist, item, group_name, duration, sort, legacy)
VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
, (DEFAULT, 21, 1, 'b', 34, 2, DEFAULT) -- !
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
ON CONFLICT (id) DO UPDATE
SET (playlist, item, group_name, duration, sort, legacy)
= (EXCLUDED.playlist, EXCLUDED.item, EXCLUDED.group_name
, EXCLUDED.duration, EXCLUDED.sort, EXCLUDED.legacy)
-- (..., COALESCE(l.legacy, EXCLUDED.legacy)) -- see below
RETURNING m.id;
Chúng tôi có thể đính kèm VALUES
mệnh đề INSERT
trực tiếp, cho phép DEFAULT
từ khóa. Trong trường hợp vi phạm duy nhất trên (id)
, Postgres cập nhật thay thế. Chúng tôi có thể sử dụng các hàng bị loại trừ trong UPDATE
. Hướng dẫn sử dụng:
SET
vàWHERE
các mệnh đề trongON CONFLICT DO UPDATE
có quyền truy cập vào hàng hiện có bằng cách sử dụng tên của bảng (hoặc một bí danh) và phân tách hàng để chèn bằng cách sử dụngexcluded
đặc biệt bảng.
Và:
Lưu ý rằng tác động của tất cả mỗi hàng
BEFORE INSERT
trình kích hoạt được phản ánh trong các giá trị bị loại trừ, vì những tác động đó có thể đã góp phần vào việc hàng bị loại trừ khỏi phần chèn.
Trường hợp góc còn lại
Bạn có nhiều tùy chọn khác nhau cho UPDATE
:Bạn có thể ...
- ... hoàn toàn không cập nhật:thêm
WHERE
mệnh đềUPDATE
để chỉ ghi vào các hàng đã chọn. - ... chỉ cập nhật các cột đã chọn.
- ... chỉ cập nhật nếu cột hiện là NULL:
COALESCE(l.legacy, EXCLUDED.legacy)
- ... chỉ cập nhật nếu giá trị mới là
NOT NULL
:COALESCE(EXCLUDED.legacy, l.legacy)
Nhưng không có cách nào để phân biệt DEFAULT
giá trị và giá trị thực sự được cung cấp trong INSERT
. Chỉ kết quả EXCLUDED
hàng có thể nhìn thấy. Nếu bạn cần sự phân biệt, hãy quay lại giải pháp trước đó, nơi bạn có cả hai giải pháp của chúng tôi.