PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Bagaimana cara mengupdate semua kolom dengan INSERT...ON CONFLICT...?

UPDATE sintaks memerlukan untuk secara eksplisit menamai kolom target. Kemungkinan alasan untuk menghindarinya:

  • Anda memiliki banyak kolom dan hanya ingin mempersingkat sintaks.
  • Anda tidak tahu nama kolom kecuali untuk kolom unik.

"All columns" harus berarti "semua kolom dari tabel target" (atau setidaknya "kolom utama tabel" ) dalam urutan yang cocok dan tipe data yang cocok. Jika tidak, Anda tetap harus memberikan daftar nama kolom target.

Tabel pengujian:

CREATE TABLE tbl (
   id    int PRIMARY KEY
 , text  text
 , extra text
);

INSERT INTO tbl AS t
VALUES (1, 'foo')
     , (2, 'bar');

1. DELETE &INSERT dalam satu permintaan saja

Tanpa mengetahui nama kolom apa pun kecuali id .

Hanya berfungsi untuk "semua kolom dari tabel target" . Sementara sintaks bahkan berfungsi untuk subset terkemuka, kolom berlebih di tabel target akan diatur ulang ke NULL dengan DELETE dan INSERT .

UPSERT (INSERT ... ON CONFLICT ... ) diperlukan untuk menghindari masalah konkurensi/penguncian di bawah beban tulis bersamaan, dan hanya karena tidak ada cara umum untuk mengunci baris yang belum ada di Postgres (penguncian nilai ).

Persyaratan khusus Anda hanya memengaruhi UPDATE bagian. Kemungkinan komplikasi tidak berlaku jika ada baris terpengaruh. Mereka terkunci dengan benar. Menyederhanakan lagi, Anda dapat mengurangi kasus Anda menjadi DELETE dan 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;

Dalam model MVCC Postgres, sebuah UPDATE sebagian besar sama dengan DELETE dan INSERT tetap (kecuali untuk beberapa kasus sudut dengan konkurensi, pembaruan PANAS, dan nilai kolom besar yang disimpan di luar jalur). Karena Anda tetap ingin mengganti semua baris, hapus saja baris yang bentrok sebelum INSERT . Baris yang dihapus tetap terkunci sampai transaksi dilakukan. INSERT mungkin hanya menemukan baris yang bertentangan untuk nilai kunci yang sebelumnya tidak ada jika transaksi bersamaan terjadi untuk memasukkannya secara bersamaan (setelah DELETE , tapi sebelum INSERT ).

Anda akan kehilangan nilai kolom tambahan untuk baris yang terpengaruh dalam kasus khusus ini. Tidak terkecuali mengangkat. Tetapi jika kueri yang bersaing memiliki prioritas yang sama, itu bukan masalah:kueri lain menang untuk beberapa baris. Juga, jika kueri lainnya adalah UPSERT yang serupa, alternatifnya adalah menunggu transaksi ini dilakukan dan kemudian segera memperbarui. "Menang" bisa jadi merupakan kemenangan yang luar biasa.

Tentang "pembaruan kosong":

  • Bagaimana cara (atau dapatkah saya) PILIH BERBEDA pada beberapa kolom?

Tidak, kueri saya harus menang!

Oke, Anda memintanya:

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;

Bagaimana?

  • data CTE pertama hanya menyediakan data. Bisa jadi meja.
  • CTE ke-2 ups :UPSERT. Baris dengan id yang bertentangan tidak diubah, tetapi juga dikunci .
  • CTE ke-3 del menghapus baris yang bertentangan. Mereka tetap terkunci.
  • CTE ke-4 ins menyisipkan seluruh baris . Hanya diperbolehkan untuk transaksi yang sama
  • PILIH terakhir hanya untuk demo untuk menunjukkan apa yang terjadi.

Untuk memeriksa tes pembaruan kosong (sebelum dan sesudah) dengan:

SELECT ctid, * FROM tbl; -- did the ctid change?

(dikomentari) memeriksa setiap perubahan pada baris AND t <> d bekerja bahkan dengan nilai NULL karena kami membandingkan dua nilai baris yang diketik menurut manual:

dua nilai bidang NULL dianggap sama, dan NULL dianggap lebih besar dari non-NULL

2. SQL Dinamis

Ini juga berfungsi untuk subset kolom utama, mempertahankan nilai yang ada.

Triknya adalah membiarkan Postgres membangun string kueri dengan nama kolom dari katalog sistem secara dinamis, lalu menjalankannya.

Lihat jawaban terkait untuk kode:

  • Perbarui beberapa kolom dalam fungsi pemicu di plpgsql

  • Pembaruan massal semua kolom

  • Bidang pembaruan SQL dari satu tabel dari bidang yang lain



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Pencocokan algoritma dalam SQL

  2. Masalah koneksi dengan SQLAlchemy dan beberapa proses

  3. Cara Menemukan Interval Antara Dua Tanggal di PostgreSQL

  4. SQLAlchemy tidak ada kesalahan yang diberikan kata sandi

  5. Strategi efektif untuk meninggalkan jejak audit/riwayat perubahan untuk aplikasi DB?