http://en.wikipedia.org/wiki/Upsert
Sisipkan Perbarui proc yang tersimpan di SQL Server
Apakah ada cara cerdas untuk melakukan ini dalam SQLite yang belum saya pikirkan?
Pada dasarnya saya ingin memperbarui tiga dari empat kolom jika catatan ada, jika tidak ada saya ingin menyisipkan catatan dengan nilai default (NUL) untuk kolom keempat.
ID adalah kunci utama sehingga hanya akan ada satu catatan untuk UPSERT.
(Saya mencoba untuk menghindari overhead SELECT untuk menentukan apakah saya perlu UPDATE atau INSERT jelas)
Saran?
Saya tidak dapat mengonfirmasi Sintaks di situs SQLite untuk TABLE CREATE. Saya belum membuat demo untuk mengujinya, tetapi sepertinya tidak didukung ..
Jika ya, saya memiliki tiga kolom sehingga sebenarnya akan terlihat seperti:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
Blob1 BLOB ON CONFLICT REPLACE,
Blob2 BLOB ON CONFLICT REPLACE,
Blob3 BLOB
);
tetapi dua gumpalan pertama tidak akan menyebabkan konflik, hanya ID akan Jadi saya asusme Blob1 dan Blob2 tidak akan diganti (seperti yang diinginkan)
UPDATE dalam SQLite saat mengikat data adalah transaksi yang lengkap, artinya Setiap baris yang dikirim untuk diperbarui membutuhkan: Mempersiapkan / Mengikat / Melangkah / Menyelesaikan pernyataan tidak seperti INSERT yang memungkinkan penggunaan fungsi reset
Kehidupan objek pernyataan berjalan seperti ini:
- Buat objek menggunakan sqlite3_prepare_v2 ()
- Mengikat nilai ke host parameter menggunakan antarmuka sqlite3_bind_.
- Jalankan SQL dengan memanggil sqlite3_step ()
- Setel ulang pernyataan menggunakan sqlite3_reset () lalu kembali ke langkah 2 dan ulangi.
- Hancurkan objek pernyataan menggunakan sqlite3_finalize ().
PEMBARUAN Saya menduga ini lambat dibandingkan dengan INSERT, tetapi bagaimana cara membandingkannya dengan SELECT menggunakan kunci Utama?
Mungkin saya harus menggunakan pilih untuk membaca kolom ke-4 (Blob3) dan kemudian menggunakan REPLACE untuk menulis catatan baru memadukan Kolom ke-4 asli dengan data baru untuk 3 kolom pertama?
Jawaban:
Dengan asumsi tiga kolom dalam tabel: ID, NAME, ROLE
BURUK: Ini akan menyisipkan atau mengganti semua kolom dengan nilai baru untuk ID = 1:
BURUK: Ini akan menyisipkan atau mengganti 2 kolom ... kolom NAME akan ditetapkan ke NULL atau nilai default:
BAIK : Gunakan SQLite Pada konflik klausa, dukungan UPSERT dalam SQLite! Sintaks UPSERT telah ditambahkan ke SQLite dengan versi 3.24.0!
UPSERT adalah tambahan sintaks khusus untuk INSERT yang menyebabkan INSERT berperilaku sebagai UPDATE atau no-op jika INSERT akan melanggar batasan keunikan. UPSERT bukan SQL standar. UPSERT dalam SQLite mengikuti sintaks yang dibuat oleh PostgreSQL.
BAIK tetapi cenderung: Ini akan memperbarui 2 kolom. Ketika ID = 1 ada, NAME tidak akan terpengaruh. Ketika ID = 1 tidak ada, nama akan menjadi default (NULL).
Ini akan memperbarui 2 kolom. Ketika ID = 1 ada, ROLE tidak akan terpengaruh. Ketika ID = 1 tidak ada, peran akan ditetapkan ke 'Pembanding Tamu' sebagai ganti nilai default.
sumber
INSERT OR REPLACE
sambil menentukan nilai untuk semua kolom.INSERT OR REPLACE adalah TIDAK setara dengan "UPSERT".
Katakanlah saya memiliki tabel Karyawan dengan id bidang, nama, dan peran:
Boom, Anda kehilangan nama nomor karyawan 1. SQLite telah menggantinya dengan nilai default.
Output yang diharapkan dari UPSERT adalah untuk mengubah peran dan mempertahankan namanya.
sumber
coalesce((select ..), 'new value')
klausa tertanam . Menurut saya, jawaban Eric membutuhkan lebih banyak suara.Jawaban Eric B adalah OK jika Anda ingin mempertahankan hanya satu atau mungkin dua kolom dari baris yang ada. Jika Anda ingin mempertahankan banyak kolom, itu menjadi terlalu rumit cepat.
Berikut adalah pendekatan yang akan mengukur dengan baik jumlah kolom di kedua sisi. Untuk menggambarkannya, saya akan mengasumsikan skema berikut:
Catatan khususnya bahwa itu
name
adalah kunci alami dari baris -id
hanya digunakan untuk kunci asing, jadi intinya adalah untuk SQLite untuk memilih nilai ID itu sendiri ketika memasukkan baris baru. Tetapi ketika memperbarui baris yang ada berdasarkan padaname
, saya ingin terus memiliki nilai ID lama (jelas!).Saya mencapai yang benar
UPSERT
dengan konstruk berikut:Bentuk persis dari kueri ini dapat sedikit berbeda. Kuncinya adalah penggunaan
INSERT SELECT
dengan gabungan luar kiri, untuk bergabung dengan baris yang ada ke nilai baru.Di sini, jika suatu baris sebelumnya tidak ada,
old.id
akan adaNULL
dan SQLite kemudian akan menetapkan ID secara otomatis, tetapi jika sudah ada baris seperti itu,old.id
akan memiliki nilai aktual dan ini akan digunakan kembali. Itulah tepatnya yang saya inginkan.Sebenarnya ini sangat fleksibel. Perhatikan bagaimana
ts
kolom tersebut benar-benar hilang di semua sisi - karena memilikiDEFAULT
nilai, SQLite hanya akan melakukan hal yang benar, jadi saya tidak perlu mengurusnya sendiri.Anda juga bisa memasukkan kolom pada kedua
new
danold
sisi dan kemudian menggunakan misalnyaCOALESCE(new.content, old.content)
di luarSELECT
untuk mengatakan “insert konten baru jika ada, jika tidak menjaga konten lama” - misalnya jika Anda menggunakan query tetap dan mengikat baru nilai dengan placeholder.sumber
WHERE name = "about"
kendala padaSELECT ... AS old
untuk mempercepat. Jika Anda memiliki 1m + baris, ini sangat lambat.WHERE
klausa seperti itu hanya membutuhkan jenis redundansi dalam kueri yang saya coba hilangkan pada awalnya ketika saya menemukan pendekatan ini. Seperti biasa: ketika Anda membutuhkan kinerja, denormalise - struktur kueri, dalam hal ini.INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
ON DELETE
memicu ketika melakukan penggantian (yaitu, pembaruan)?ON DELETE
pemicu. Tidak tahu tentang yang tidak perlu. Untuk sebagian besar pengguna, itu mungkin tidak perlu, bahkan tidak diinginkan, tetapi mungkin tidak untuk semua pengguna. Demikian juga untuk fakta bahwa itu juga akan menghapus-baris apa pun dengan kunci asing ke baris yang dimaksud - mungkin masalah bagi banyak pengguna. SQLite tidak memiliki yang lebih dekat dengan UPSERT nyata, sayangnya. (Simpan untuk berpura-pura denganINSTEAD OF UPDATE
pemicu, saya kira.)Jika Anda umumnya melakukan pembaruan saya akan ..
Jika Anda umumnya melakukan sisipan saya akan
Dengan cara ini Anda menghindari pilih dan Anda transaksi suara di Sqlite.
sumber
Jawaban ini telah diperbarui dan komentar di bawah tidak berlaku lagi.
2018-05-18 STOP PRESS.
Dukungan UPSERT dalam SQLite!Sintaks UPSERT telah ditambahkan ke SQLite dengan versi 3.24.0 (tertunda)!
UPSERT adalah tambahan sintaks khusus untuk INSERT yang menyebabkan INSERT berperilaku sebagai UPDATE atau no-op jika INSERT akan melanggar batasan keunikan. UPSERT bukan SQL standar. UPSERT dalam SQLite mengikuti sintaks yang dibuat oleh PostgreSQL.
kalau tidak:
Cara lain yang sama sekali berbeda untuk melakukan ini adalah: Dalam aplikasi saya, saya menetapkan rowID di memori saya menjadi long.MaxValue ketika saya membuat baris di memori. (MaxValue tidak akan pernah digunakan sebagai ID Anda tidak akan hidup cukup lama .... Kemudian jika rowID bukan nilai itu maka harus sudah ada dalam database sehingga perlu UPDATE jika itu MaxValue maka perlu disisipkan. Ini hanya berguna jika Anda dapat melacak rowIDs di aplikasi Anda.
sumber
WHERE
klausa tidak dapat ditambahkan keINSERT
pernyataan: sqlite-insert ...Saya menyadari ini adalah utas lama tetapi saya telah bekerja di sqlite3 pada akhir-akhir ini dan menghasilkan metode ini yang lebih sesuai dengan kebutuhan saya secara dinamis menghasilkan kueri parameter:
Ini masih 2 pertanyaan dengan klausa di mana pada pembaruan tetapi tampaknya melakukan trik. Saya juga memiliki visi ini di kepala saya bahwa sqlite dapat mengoptimalkan pernyataan pembaruan sepenuhnya jika panggilan ke perubahan () lebih besar dari nol. Apakah itu benar-benar atau tidak di luar pengetahuan saya, tetapi seorang pria dapat bermimpi bukan? ;)
Untuk poin bonus, Anda dapat menambahkan baris ini yang mengembalikan id baris apakah itu baris baru atau baris yang sudah ada.
sumber
Update <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
Berikut ini adalah solusi yang benar-benar merupakan UPSERT (UPDATE atau INSERT) daripada INSERT ATAU PENGGANTIAN (yang bekerja secara berbeda dalam banyak situasi).
Ini berfungsi seperti ini:
1. Cobalah untuk memperbarui jika ada catatan dengan Id yang sama.
2. Jika pembaruan tidak mengubah baris apa pun (
NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
), lalu masukkan catatan.Jadi, catatan yang ada diperbarui atau disisipkan akan dilakukan.
Detail penting adalah menggunakan perubahan () fungsi SQL untuk memeriksa apakah pernyataan pembaruan mengenai catatan yang ada dan hanya melakukan pernyataan memasukkan jika tidak mencapai catatan apa pun.
Satu hal yang perlu disebutkan adalah bahwa fungsi perubahan () tidak mengembalikan perubahan yang dilakukan oleh pemicu tingkat rendah (lihat http://sqlite.org/lang_corefunc.html#changes ), jadi pastikan untuk memperhitungkannya.
Berikut adalah SQL ...
Pembaruan tes:
Sisipan uji:
sumber
INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;
juga harus bekerja.Dimulai dengan versi 3.24.0 UPSERT didukung oleh SQLite.
Dari dokumentasi :
Sumber gambar: https://www.sqlite.org/images/syntax/upsert-clause.gif
sumber
Anda memang dapat melakukan upsert dalam SQLite, itu hanya terlihat sedikit berbeda dari yang biasa Anda lakukan. Itu akan terlihat seperti:
sumber
Memperluas jawaban Aristoteles Anda dapat MEMILIH dari tabel 'tunggal' boneka (tabel ciptaan Anda sendiri dengan satu baris). Ini menghindari duplikasi.
Saya juga menyimpan contoh portable di MySQL dan SQLite dan menggunakan kolom 'date_added' sebagai contoh bagaimana Anda bisa mengatur kolom hanya pertama kali.
sumber
Pendekatan terbaik yang saya tahu adalah melakukan pembaruan, diikuti oleh sisipan. "Overhead pilihan" diperlukan, tetapi itu bukan beban yang mengerikan karena Anda mencari pada kunci utama, yang cepat.
Anda harus dapat mengubah pernyataan di bawah ini dengan nama tabel & bidang Anda untuk melakukan apa yang Anda inginkan.
sumber
Jika seseorang ingin membaca solusi saya untuk SQLite di Cordova, saya mendapatkan metode js generik ini karena jawaban @david di atas.
Jadi, pertama-tama ambil nama kolom dengan fungsi ini:
Kemudian bangun transaksi secara terprogram.
"Nilai" adalah array yang harus Anda bangun sebelumnya dan itu mewakili baris yang ingin Anda masukkan atau perbarui ke dalam tabel.
"remoteid" adalah id yang saya gunakan sebagai referensi, karena saya menyinkronkan dengan server jarak jauh saya.
Untuk penggunaan plugin SQLite Cordova, silakan merujuk ke tautan resmi
sumber
Metode ini mencampur beberapa metode lain dari jawaban untuk pertanyaan ini dan menggabungkan penggunaan CTE (Common Table Expressions). Saya akan memperkenalkan kueri lalu menjelaskan mengapa saya melakukan apa yang saya lakukan.
Saya ingin mengubah nama belakang untuk 300 karyawan menjadi DAVIS jika ada 300 karyawan. Jika tidak, saya akan menambah karyawan baru.
Nama Tabel: pegawai Kolom: id, first_name, last_name
Pertanyaannya adalah:
Pada dasarnya, saya menggunakan CTE untuk mengurangi berapa kali pernyataan pilih harus digunakan untuk menentukan nilai default. Karena ini adalah CTE, kami cukup memilih kolom yang kami inginkan dari tabel dan pernyataan INSERT menggunakan ini.
Sekarang Anda dapat memutuskan default apa yang ingin Anda gunakan dengan mengganti nulls, dalam fungsi COALESCE dengan nilai apa yang seharusnya.
sumber
Mengikuti Aristoteles Pagaltzis dan ide
COALESCE
dari jawaban Eric B , di sini itu adalah pilihan upsert untuk memperbarui hanya beberapa kolom atau menyisipkan baris penuh jika itu tidak ada.Dalam hal ini, bayangkan judul dan konten harus diperbarui, menjaga nilai lama lainnya saat ada dan memasukkan yang disediakan ketika nama tidak ditemukan:
CATATAN
id
dipaksa menjadi NULL ketikaINSERT
seharusnya dianggap sebagai peningkatan otomatis. Jika itu hanya kunci primer yang dihasilkan makaCOALESCE
juga dapat digunakan (lihat komentar Aristoteles Pagaltzis ).Jadi aturan umumnya adalah, jika Anda ingin menyimpan nilai lama, gunakan
COALESCE
, ketika Anda ingin memperbarui nilai, gunakannew.fieldname
sumber
COALESCE(old.id, new.id)
jelas salah dengan kunci peningkatan otomatis. Dan sementara "menjaga sebagian besar baris tidak berubah, kecuali di mana nilai-nilai hilang" terdengar seperti kasus penggunaan yang mungkin dimiliki seseorang, saya tidak berpikir itulah yang dicari orang ketika mereka mencari cara melakukan UPSERT.old
tabel tempat ditugaskanNULL
, bukan ke nilai yang disediakannew
. Inilah alasan untuk menggunakannyaCOALESCE
. Saya bukan ahli dalam sqlite, saya telah menguji permintaan ini dan tampaknya bekerja untuk kasus ini, saya akan sangat berterima kasih jika Anda bisa mengarahkan saya ke solusi dengan autoincrementsNULL
sebagai kunci, karena itu memberitahu SQLite untuk memasukkan nilai berikutnya yang tersedia.Saya pikir ini mungkin yang Anda cari: ON CONFLICT klausa .
Jika Anda mendefinisikan tabel Anda seperti ini:
Sekarang, jika Anda melakukan INSERT dengan id yang sudah ada, SQLite secara otomatis melakukan UPDATE dan bukan INSERT.
Hth ...
sumber
REPLACE
pernyataan.Jika Anda tidak keberatan melakukan ini dalam dua operasi.
Langkah:
1) Tambahkan item baru dengan "INSERT OR IGNORE"
2) Perbarui item yang ada dengan "PEMBARUAN"
Input untuk kedua langkah adalah koleksi yang sama dari item baru atau yang dapat diperbarui. Bekerja dengan baik dengan item yang ada yang tidak perlu diubah. Mereka akan diperbarui, tetapi dengan data yang sama dan karenanya hasil bersih tidak ada perubahan.
Tentu, lebih lambat, dll. Tidak efisien. Ya.
Mudah menulis sql dan memelihara dan memahaminya? Pastinya.
Ini adalah trade-off untuk dipertimbangkan. Berfungsi bagus untuk upert kecil. Berfungsi bagus untuk mereka yang tidak keberatan mengorbankan efisiensi untuk pemeliharaan kode.
sumber
Baru saja membaca utas ini dan kecewa karena tidak mudah hanya dengan "UPSERT" ini, saya menyelidiki lebih lanjut ...
Anda sebenarnya dapat melakukan ini secara langsung dan mudah dalam SQLITE.
Alih-alih menggunakan:
INSERT INTO
Menggunakan:
INSERT OR REPLACE INTO
Ini melakukan persis seperti yang Anda inginkan!
sumber
INSERT OR REPLACE
bukanUPSERT
. Lihat "jawaban" gregschlom untuk alasan mengapa. Solusi Eric B benar-benar berfungsi dan membutuhkan beberapa perbaikan.jika
COUNT(*) = 0
lain jika
COUNT(*) > 0
sumber