Beberapa bulan yang lalu saya belajar dari jawaban di Stack Overflow bagaimana melakukan beberapa pembaruan sekaligus di MySQL menggunakan sintaks berikut:
INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);
Saya sekarang telah beralih ke PostgreSQL dan ternyata ini tidak benar. Ini merujuk pada semua tabel yang benar jadi saya menganggap itu masalah kata kunci yang berbeda yang digunakan, tetapi saya tidak yakin di mana dalam dokumentasi PostgreSQL ini dibahas.
Untuk memperjelas, saya ingin memasukkan beberapa hal dan jika sudah ada untuk memperbaruinya.
sql
postgresql
upsert
sql-merge
Teifion
sumber
sumber
Jawaban:
PostgreSQL sejak versi 9.5 memiliki sintaks UPSERT , dengan klausa ON CONFLICT . dengan sintaks berikut (mirip dengan MySQL)
Mencari arsip grup email postgresql untuk "upert" mengarah ke menemukan contoh melakukan apa yang mungkin ingin Anda lakukan, dalam manual :
Mungkin ada contoh bagaimana melakukan ini secara massal, menggunakan CTE di 9.1 dan di atas, di milis peretas :
Lihat jawaban a_horse_with_no_name untuk contoh yang lebih jelas.
sumber
excluded
dengan solusi pertama di sini?excluded
tabel khusus memberi Anda akses ke nilai yang Anda coba INSERT.Peringatan: ini tidak aman jika dijalankan dari beberapa sesi secara bersamaan (lihat peringatan di bawah).
Cara pintar lain untuk melakukan "UPSERT" di postgresql adalah dengan melakukan dua pernyataan UPDATE / INSERT berurutan yang masing-masing dirancang untuk berhasil atau tidak berpengaruh.
UPDATE akan berhasil jika baris dengan "id = 3" sudah ada, jika tidak maka tidak akan berpengaruh.
INSERT hanya akan berhasil jika baris dengan "id = 3" tidak ada.
Anda dapat menggabungkan keduanya menjadi satu string dan menjalankan keduanya dengan mengeksekusi pernyataan SQL tunggal dari aplikasi Anda. Sangat disarankan untuk menjalankannya bersama dalam satu transaksi.
Ini bekerja dengan sangat baik ketika dijalankan dalam isolasi atau di atas meja yang terkunci, tetapi tunduk pada kondisi balapan yang berarti ia mungkin masih gagal dengan kesalahan kunci duplikat jika sebuah baris dimasukkan secara bersamaan, atau mungkin berakhir tanpa baris yang dimasukkan ketika sebuah baris dihapus bersamaan. . SEBUAH
SERIALIZABLE
transaksi pada PostgreSQL 9.1 atau lebih tinggi akan menanganinya dengan andal dengan biaya tingkat kegagalan serialisasi yang sangat tinggi, artinya Anda harus banyak mencoba. Lihat mengapa begitu rumit , yang membahas kasus ini secara lebih rinci.Pendekatan ini juga tunduk pada pembaruan yang hilang secara
read committed
terpisah kecuali jika aplikasi memeriksa jumlah baris yang terpengaruh dan memverifikasi bahwa salah satuinsert
atau baris yangupdate
terpengaruh .sumber
... where not exists (select 1 from table where id = 3);
read committed
dapat menyebabkan pembaruan yang hilang secara terpisah kecuali jika aplikasi Anda memeriksa untuk memastikan bahwainsert
atauupdate
memiliki jumlah baris yang tidak nol. Lihat dba.stackexchange.com/q/78510/7788Dengan PostgreSQL 9.1 ini dapat dicapai menggunakan CTE yang dapat ditulis ( ekspresi tabel umum ):
Lihat entri blog ini:
Perhatikan bahwa solusi ini tidak mencegah pelanggaran kunci unik tetapi tidak rentan terhadap pembaruan yang hilang.
Lihat tindak lanjut oleh Craig Ringer di dba.stackexchange.com
sumber
UPDATE
baris yang terpengaruh terpengaruh.Di PostgreSQL 9.5 dan yang lebih baru, Anda dapat menggunakan
INSERT ... ON CONFLICT UPDATE
.Lihat dokumentasi .
MySQL
INSERT ... ON DUPLICATE KEY UPDATE
dapat secara langsung diulang menjadiON CONFLICT UPDATE
. Sintaks standar-SQL juga tidak, keduanya merupakan ekstensi khusus basis data. Ada alasan bagusMERGE
yang tidak digunakan untuk ini , sintaks baru tidak dibuat hanya untuk bersenang-senang. (Sintaks MySQL juga memiliki masalah yang berarti tidak diadopsi secara langsung).mis. pengaturan yang diberikan:
permintaan MySQL:
menjadi:
Perbedaan:
Anda harus menentukan nama kolom (atau nama kendala unik) yang akan digunakan untuk pemeriksaan keunikan. Itu adalah
ON CONFLICT (columnname) DO
Kata kunci
SET
harus digunakan, seolah-olah ini adalahUPDATE
pernyataan normalIni memiliki beberapa fitur bagus juga:
Anda dapat memiliki
WHERE
klausa pada AndaUPDATE
(membiarkan Anda secara efektif berubahON CONFLICT UPDATE
menjadiON CONFLICT IGNORE
untuk nilai-nilai tertentu)Nilai usulan untuk penyisipan tersedia sebagai variabel baris
EXCLUDED
, yang memiliki struktur yang sama dengan tabel target. Anda bisa mendapatkan nilai asli di tabel dengan menggunakan nama tabel. Jadi dalam hal iniEXCLUDED.c
akan10
(karena itulah yang kami coba masukkan) dan"table".c
akan3
karena itulah nilai saat ini dalam tabel. Anda dapat menggunakan salah satu atau keduanya dalamSET
ekspresi danWHERE
klausa.Untuk latar belakang tentang upsert, lihat Cara UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) di PostgreSQL?
sumber
ON DUPLICATE KEY UPDATE
. Saya telah mengunduh Postgres 9.5 dan menerapkan kode Anda, tetapi anehnya masalah yang sama terjadi di bawah Postgres: bidang serial primary key tidak berurutan (ada celah antara sisipan dan pembaruan.). Adakah yang tahu apa yang terjadi di sini? Apakah ini normal? Adakah yang tahu bagaimana cara menghindari perilaku ini? Terima kasih.SERIAL
/SEQUENCE
atauAUTO_INCREMENT
tidak memiliki kesenjangan. Jika Anda membutuhkan urutan tanpa celah mereka lebih kompleks; Anda perlu menggunakan meja penghitung biasanya. Google akan memberi tahu Anda lebih banyak. Namun berhati-hatilah, sekuens gapless mencegah semua memasukkan konkurensi.BEGIN ... EXCEPTION ...
dalam subtransaksi yang dibatalkan karena kesalahan, kenaikan urutan Anda akan dibatalkan jikaINSERT
gagal.Saya sedang mencari hal yang sama ketika saya datang ke sini, tetapi kurangnya fungsi "upsert" generik sedikit mengganggu saya, jadi saya pikir Anda bisa melewati pembaruan dan memasukkan sql sebagai argumen pada fungsi yang membentuk manual
akan terlihat seperti ini:
dan mungkin untuk melakukan apa yang awalnya ingin Anda lakukan, batch "upsert", Anda bisa menggunakan Tcl untuk membagi sql_update dan loop pembaruan individu, hit dalm kinerja akan sangat kecil lihat http://archives.postgresql.org/pgsql- performance / 2006-04 / msg00557.php
biaya tertinggi adalah mengeksekusi kueri dari kode Anda, di sisi database biaya eksekusi jauh lebih kecil
sumber
DELETE
kecuali Anda mengunci tabel atau berada dalamSERIALIZABLE
isolasi transaksi pada PostgreSQL 9.1 atau lebih besar.Tidak ada perintah sederhana untuk melakukannya.
Pendekatan yang paling benar adalah dengan menggunakan fungsi, seperti yang dari dokumen .
Solusi lain (meskipun tidak aman) adalah melakukan pembaruan dengan mengembalikan, memeriksa baris mana yang diperbarui, dan menyisipkan sisanya
Sesuatu di sepanjang garis:
dengan asumsi id: 2 dikembalikan:
Tentu saja itu akan menebus cepat atau lambat (di lingkungan bersamaan), karena ada kondisi balapan yang jelas di sini, tetapi biasanya itu akan berhasil.
Inilah artikel yang lebih panjang dan lebih komprehensif tentang topik ini .
sumber
Secara pribadi, saya telah membuat "aturan" yang terlampir pada pernyataan insert. Katakanlah Anda memiliki tabel "dns" yang mencatat hit dns per pelanggan berdasarkan per-waktu:
Anda ingin dapat memasukkan kembali baris dengan nilai yang diperbarui, atau membuatnya jika belum ada. Mengetik pada customer_id dan waktu. Sesuatu seperti ini:
Pembaruan: Ini berpotensi gagal jika memasukkan secara bersamaan, karena akan menghasilkan pengecualian unique_violation. Namun, transaksi yang tidak dihentikan akan berlanjut dan berhasil, dan Anda hanya perlu mengulangi transaksi yang dihentikan.
Namun, jika ada banyak sisipan yang terjadi sepanjang waktu, Anda harus meletakkan kunci meja di sekitar pernyataan penyisipan: SHARE ROW EKSKLUSIF mengunci akan mencegah operasi apa pun yang bisa menyisipkan, menghapus atau memperbarui baris di tabel target Anda. Namun, pembaruan yang tidak memperbarui kunci unik aman, jadi jika Anda tidak melakukan operasi akan melakukan ini, gunakan kunci penasihat sebagai gantinya.
Selain itu, perintah COPY tidak menggunakan ATURAN, jadi jika Anda memasukkan dengan COPY, Anda harus menggunakan pemicu.
sumber
Saya menggunakan fungsi ini menggabungkan
sumber
update
pertama dan kemudian memeriksa jumlah baris yang diperbarui. (Lihat jawaban Ahmad)Saya kustom fungsi "upsert" di atas, jika Anda ingin menyisipkan DAN MENGGANTI:
`
Dan setelah dieksekusi, lakukan sesuatu seperti ini:
Sangat penting untuk menempatkan koma dolar ganda untuk menghindari kesalahan kompiler
sumber
Mirip dengan jawaban yang paling disukai, tetapi bekerja sedikit lebih cepat:
(sumber: http://www.the-art-of-web.com/sql/upsert/ )
sumber
Saya memiliki masalah yang sama untuk mengelola pengaturan akun sebagai pasangan nilai nama. Kriteria desain adalah bahwa klien yang berbeda dapat memiliki set pengaturan yang berbeda.
Solusi saya, mirip dengan JWP adalah menghapus dan mengganti secara massal, menghasilkan catatan gabungan dalam aplikasi Anda.
Ini cukup antipeluru, platform independen dan karena tidak pernah ada lebih dari sekitar 20 pengaturan per klien, ini hanya 3 panggilan db beban yang cukup rendah - mungkin metode tercepat.
Alternatif memperbarui setiap baris - memeriksa pengecualian kemudian memasukkan - atau kombinasi dari kode yang mengerikan, lambat dan sering rusak karena (seperti yang disebutkan di atas) penanganan pengecualian non standar SQL berubah dari db ke db - atau bahkan rilis untuk dirilis.
sumber
REPLACE INTO
daripadaINSERT INTO ... ON DUPLICATE KEY UPDATE
, yang dapat menyebabkan masalah jika Anda menggunakan pemicu. Anda pada akhirnya akan menjalankan hapus dan masukkan pemicu / aturan, bukan yang diperbarui.Menurut dokumentasi
INSERT
pernyataan PostgreSQL , penangananON DUPLICATE KEY
kasus tidak didukung. Itu bagian dari sintaks adalah ekstensi MySQL milik.sumber
MERGE
juga benar-benar lebih dari operasi OLAP; lihat stackoverflow.com/q/17267417/398670 untuk penjelasannya. Itu tidak mendefinisikan semantik konkurensi dan kebanyakan orang yang menggunakannya untuk upert hanya membuat bug.sumber
Untuk menggabungkan set kecil, menggunakan fungsi di atas baik-baik saja. Namun, jika Anda menggabungkan data dalam jumlah besar, saya sarankan melihat ke http://mbk.projects.postgresql.org
Praktik terbaik saat ini yang saya ketahui adalah:
sumber
UPDATE akan mengembalikan jumlah baris yang dimodifikasi. Jika Anda menggunakan JDBC (Java), Anda dapat memeriksa nilai ini terhadap 0 dan, jika tidak ada baris yang terpengaruh, jalankan INSERT. Jika Anda menggunakan bahasa pemrograman lain, mungkin jumlah baris yang dimodifikasi masih dapat diperoleh, periksa dokumentasi.
Ini mungkin tidak elegan, tetapi Anda memiliki SQL sederhana yang lebih sepele untuk digunakan dari kode panggilan. Secara berbeda, jika Anda menulis skrip sepuluh baris dalam PL / PSQL, Anda mungkin harus memiliki unit test dari satu atau jenis lain hanya untuk itu saja.
sumber
Sunting: Ini tidak berfungsi seperti yang diharapkan. Tidak seperti jawaban yang diterima, ini menghasilkan pelanggaran kunci unik ketika dua proses berulang kali memanggil
upsert_foo
secara bersamaan.Eureka! Saya menemukan cara untuk melakukannya dalam satu permintaan: gunakan
UPDATE ... RETURNING
untuk menguji apakah ada baris yang terpengaruh:The
UPDATE
harus dilakukan dalam prosedur yang terpisah karena, sayangnya, ini adalah kesalahan sintaks:Sekarang berfungsi sesuai keinginan:
sumber