Pertanyaan yang sangat sering diajukan di sini adalah bagaimana melakukan upert, yang disebut dengan panggilan MySQL INSERT ... ON DUPLICATE UPDATE
dan standar sebagai bagian dari MERGE
operasi.
Mengingat PostgreSQL tidak mendukungnya secara langsung (sebelum pg 9.5), bagaimana Anda melakukan ini? Pertimbangkan yang berikut ini:
CREATE TABLE testtable (
id integer PRIMARY KEY,
somedata text NOT NULL
);
INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
Sekarang bayangkan bahwa Anda ingin "upsert" tuple (2, 'Joe')
, (3, 'Alan')
sehingga isi tabel baru akan:
(1, 'fred'),
(2, 'Joe'), -- Changed value of existing tuple
(3, 'Alan') -- Added new tuple
Itulah yang dibicarakan orang ketika mendiskusikan sebuah upsert
. Yang terpenting, pendekatan apa pun harus aman di hadapan beberapa transaksi yang bekerja di meja yang sama - baik dengan menggunakan penguncian eksplisit, atau mempertahankan terhadap kondisi balapan yang dihasilkan.
Topik ini dibahas secara luas di Sisipkan, tentang pembaruan duplikat di PostgreSQL? , tapi itu tentang alternatif untuk sintaks MySQL, dan itu tumbuh sedikit detail yang tidak terkait dari waktu ke waktu. Saya sedang mengerjakan jawaban yang pasti.
Teknik-teknik ini juga berguna untuk "masukkan jika tidak ada, jika tidak lakukan apa-apa", yaitu "masukkan ... pada kunci duplikat abaikan".
sumber
Jawaban:
9.5 dan yang lebih baru:
PostgreSQL 9.5 dan dukungan yang lebih baru
INSERT ... ON CONFLICT UPDATE
(danON CONFLICT DO NOTHING
), yaitu upert.Perbandingan dengan
ON DUPLICATE KEY UPDATE
.Penjelasan cepat .
Untuk penggunaan melihat manual - khususnya conflict_action klausul dalam diagram sintaks, dan teks jelas .
Berbeda dengan solusi untuk 9.4 dan lebih lama yang diberikan di bawah ini, fitur ini bekerja dengan beberapa baris yang saling bertentangan dan tidak memerlukan penguncian eksklusif atau coba lagi loop.
Komit yang menambahkan fitur ada di sini dan diskusi seputar pengembangannya ada di sini .
Jika Anda menggunakan 9.5 dan tidak perlu kompatibel-mundur Anda dapat berhenti membaca sekarang .
9.4 dan lebih lama:
PostgreSQL tidak memiliki fasilitas built-in
UPSERT
(atauMERGE
), dan melakukannya secara efisien dalam menghadapi penggunaan bersamaan sangat sulit.Artikel ini membahas masalah dengan detail yang bermanfaat .
Secara umum Anda harus memilih antara dua opsi:
Pengulangan coba baris individual
Menggunakan masing-masing baris upert dalam loop coba adalah pilihan yang masuk akal jika Anda ingin banyak koneksi secara bersamaan mencoba melakukan sisipan.
Dokumentasi PostgreSQL berisi prosedur bermanfaat yang memungkinkan Anda melakukan ini dalam satu lingkaran di dalam basis data . Ini melindungi terhadap pembaruan yang hilang dan menyisipkan balapan, tidak seperti kebanyakan solusi naif. Ini hanya akan bekerja dalam
READ COMMITTED
mode dan hanya aman jika itu adalah satu-satunya hal yang Anda lakukan dalam transaksi. Fungsi tidak akan berfungsi dengan benar jika pemicu atau kunci unik sekunder menyebabkan pelanggaran unik.Strategi ini sangat tidak efisien. Kapan pun Anda praktis, Anda harus mengantri kerja dan melakukan upert massal seperti yang dijelaskan di bawah ini.
Banyak upaya solusi untuk masalah ini gagal untuk mempertimbangkan rollback, sehingga menghasilkan pembaruan yang tidak lengkap. Dua transaksi saling bersaing; salah satunya berhasil
INSERT
; yang lain mendapat kesalahan kunci duplikat dan melakukanUPDATE
sebaliknya. TheUPDATE
blok menungguINSERT
untuk rollback atau melakukan. Ketika bergulir kembali,UPDATE
kondisi pemeriksaan ulang cocok dengan baris nol, jadi meskipunUPDATE
komit tidak benar-benar melakukan upert yang Anda harapkan. Anda harus memeriksa jumlah baris hasil dan mencoba kembali jika perlu.Beberapa solusi yang dicoba juga gagal untuk mempertimbangkan ras SELECT. Jika Anda mencoba yang jelas dan sederhana:
maka ketika dua dijalankan sekaligus ada beberapa mode kegagalan. Salah satunya adalah masalah yang sudah dibahas dengan pembaruan ulang. Lain adalah di mana keduanya
UPDATE
pada saat yang sama, mencocokkan nol baris dan melanjutkan. Kemudian mereka berdua melakukanEXISTS
tes, yang terjadi sebelum ituINSERT
. Keduanya mendapatkan nol baris, jadi keduanya melakukanINSERT
. Satu gagal dengan kesalahan kunci duplikat.Inilah sebabnya mengapa Anda perlu mencoba ulang lingkaran. Anda mungkin berpikir bahwa Anda dapat mencegah kesalahan kunci duplikat atau kehilangan pembaruan dengan SQL pintar, tetapi Anda tidak bisa. Anda perlu memeriksa jumlah baris atau menangani kesalahan kunci duplikat (tergantung pada pendekatan yang dipilih) dan coba lagi.
Tolong jangan roll solusi Anda sendiri untuk ini. Seperti halnya antrian pesan, itu mungkin salah.
Upert massal dengan kunci
Kadang-kadang Anda ingin melakukan bulk upsert, di mana Anda memiliki kumpulan data baru yang ingin Anda gabungkan ke dalam kumpulan data lama yang sudah ada. Ini jauh lebih efisien daripada baris individual dan harus lebih disukai jika praktis.
Dalam hal ini, Anda biasanya mengikuti proses berikut:
CREATE
sebuahTEMPORARY
mejaCOPY
atau massal-memasukkan data baru ke tabel tempLOCK
tabel targetIN EXCLUSIVE MODE
. Ini memungkinkan transaksi lain untukSELECT
, tetapi tidak membuat perubahan apa pun pada tabel.Lakukan
UPDATE ... FROM
catatan yang ada menggunakan nilai-nilai dalam tabel temp;Lakukan
INSERT
baris yang belum ada di tabel target;COMMIT
, melepaskan kunci.Misalnya, untuk contoh yang diberikan dalam pertanyaan, menggunakan multi-nilai
INSERT
untuk mengisi tabel temp:Bacaan terkait
MERGE
pada wiki PostgreSQLBagaimana dengan
MERGE
?SQL-standar
MERGE
sebenarnya memiliki semantik konkurensi yang tidak jelas dan tidak cocok untuk memasang tanpa mengunci meja terlebih dahulu.Ini adalah pernyataan OLAP yang sangat berguna untuk penggabungan data, tetapi sebenarnya bukan solusi yang berguna untuk uperturrency yang aman. Ada banyak saran untuk orang-orang yang menggunakan DBMS lain untuk digunakan
MERGE
untuk upert, tetapi sebenarnya itu salah.DB lain:
INSERT ... ON DUPLICATE KEY UPDATE
di MySQLMERGE
dari MS SQL Server (tetapi lihat di atas tentangMERGE
masalah)MERGE
dari Oracle (tetapi lihat di atas tentangMERGE
masalah)sumber
MERGE
untuk SQL Server dan Oracle tidak benar dan rentan terhadap kondisi balapan, seperti disebutkan di atas. Anda harus melihat ke dalam setiap DBMS secara khusus untuk mengetahui cara menanganinya, saya benar-benar hanya dapat memberikan saran tentang PostgreSQL. Satu-satunya cara untuk melakukan upsert multi-baris yang aman pada PostgreSQL adalah jika dukungan untuk native upsert ditambahkan ke server inti.Saya mencoba berkontribusi dengan solusi lain untuk masalah penyisipan tunggal dengan versi pre-9.5 PostgreSQL. Idenya adalah hanya untuk mencoba melakukan penyisipan terlebih dahulu, dan jika ada catatan, untuk memperbaruinya:
Perhatikan bahwa solusi ini hanya dapat diterapkan jika tidak ada penghapusan baris tabel .
Saya tidak tahu tentang efisiensi solusi ini, tetapi menurut saya cukup masuk akal.
sumber
insert on update
Berikut adalah beberapa contoh untuk
insert ... on conflict ...
( hal 9.5+ ):sumber
Upayakan SQLAlchemy untuk Postgres> = 9.5
Karena postingan besar di atas mencakup banyak pendekatan SQL yang berbeda untuk versi Postgres (tidak hanya non-9.5 seperti pada pertanyaan), saya ingin menambahkan cara melakukannya dalam SQLAlchemy jika Anda menggunakan Postgres 9.5. Alih-alih mengimplementasikan upsert Anda sendiri, Anda juga dapat menggunakan fungsi SQLAlchemy (yang ditambahkan dalam SQLAlchemy 1.1). Secara pribadi, saya akan merekomendasikan menggunakan ini, jika memungkinkan. Bukan hanya karena kenyamanan, tetapi juga karena memungkinkan PostgreSQL menangani semua kondisi lomba yang mungkin terjadi.
Posting silang dari jawaban lain yang saya berikan kemarin ( https://stackoverflow.com/a/44395983/2156909 )
SQLAlchemy mendukung
ON CONFLICT
sekarang dengan dua metodeon_conflict_do_update()
danon_conflict_do_nothing()
:Menyalin dari dokumentasi:
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert
sumber
Diuji pada Postgresql 9.3
sumber
SERIALIZABLE
isolasi Anda akan dibatalkan dengan kegagalan serialisasi, jika tidak, Anda mungkin akan mendapatkan pelanggaran unik. Jangan menemukan kembali upert, reinvention akan salah. GunakanINSERT ... ON CONFLICT ...
. Jika PostgreSQL Anda terlalu lama, perbarui.INSERT ... ON CLONFLICT ...
tidak dimaksudkan untuk memuat massal. Dari pos Anda,LOCK TABLE testtable IN EXCLUSIVE MODE;
dalam CTE adalah solusi untuk mendapatkan hal-hal atom. Tidak ?insert ... where not exists ...
atau serupa, tentu saja.Karena pertanyaan ini sudah ditutup, saya memposting di sini untuk bagaimana Anda melakukannya menggunakan SQLAlchemy. Melalui rekursi, ia mencoba kembali memasukkan atau memperbarui massal untuk memerangi kondisi balapan dan kesalahan validasi.
Pertama impor
Sekarang fungsi pembantu pasangan
Dan akhirnya fungsi upert
Begini cara Anda menggunakannya
Keuntungan dari ini
bulk_save_objects
adalah bahwa ia dapat menangani hubungan, pengecekan kesalahan, dll saat penyisipan (tidak seperti operasi massal ).sumber
SERIALIZABLE
transaksi dan menangani kegagalan serialisasi tetapi lambat. Anda perlu penanganan kesalahan dan coba lagi loop. Lihat jawaban saya dan bagian "bacaan terkait" di dalamnya.