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