PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Tạo các giá trị DEFAULT trong CTE UPSERT bằng PostgreSQL 9.3

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ủa INSERT , có thể thay thế biểu thức khác bằng DEFAULT để 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 khi VALUES 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ặc IMMUTABLE biểu thức mặc định . Hầu hết VOLATILE 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ới serial 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ào serial các cột trực tiếp. Những thứ đó không nên được liệt kê trong INSERT tất cả các câu lệnh.
    Lỗ hổng còn lại cho serial 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 trong serial 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ằng NULL 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:

SETWHERE các mệnh đề trong ON 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ụng excluded đặ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.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Gói PGLogical 1.1 cho PostgreSQL 9.6beta1

  2. Cách Tan () hoạt động trong PostgreSQL

  3. Làm cách nào để lấy giá trị từ hàng được chèn cuối cùng?

  4. Rails phương thức không xác định cho ActiveRecord_Associations_CollectionProxy

  5. Làm cách nào để tạo bản sao lưu của một bảng trong cơ sở dữ liệu postgres?