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

Làm cách nào để cập nhật tất cả các cột bằng CHÈN ... BẬT MẶT ...?

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 DELETEINSERT .

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 DELETEINSERT :

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 DELETEINSERT 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



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Ổ cứng thể rắn Intel SSD, hiện đã ra khỏi danh sách sh..err, shamed

  2. Chuỗi UTF-8 tương tự cho trường tự động hoàn thành

  3. Cách khai báo biến trong PostgreSQL

  4. Làm thế nào để sử dụng (cài đặt) dblink trong PostgreSQL?

  5. Cách tải PostgreSQL trên VPS / Máy chủ chuyên dụng