SQLite - UPSERT * bukan * INSERT atau REPLACE

535

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:

  1. Buat objek menggunakan sqlite3_prepare_v2 ()
  2. Mengikat nilai ke host parameter menggunakan antarmuka sqlite3_bind_.
  3. Jalankan SQL dengan memanggil sqlite3_step ()
  4. Setel ulang pernyataan menggunakan sqlite3_reset () lalu kembali ke langkah 2 dan ulangi.
  5. 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?

Mike Trader
sumber
5
SQLite - UPSERT tersedia dalam referensi
gaurav
3
UPSERT tersedia dalam versi 3.24.0 dari SQLite
pablo_worker

Jawaban:

854

Dengan asumsi tiga kolom dalam tabel: ID, NAME, ROLE


BURUK: Ini akan menyisipkan atau mengganti semua kolom dengan nilai baru untuk ID = 1:

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

BURUK: Ini akan menyisipkan atau mengganti 2 kolom ... kolom NAME akan ditetapkan ke NULL atau nilai default:

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

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.

masukkan deskripsi gambar di sini

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).

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

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.

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );
Eric B
sumber
33
+1 brilian! Klausa pilihan yang disematkan memberi Anda fleksibilitas untuk mengganti fungsi ON CONFLICT REPLACE default jika Anda perlu menggabungkan / membandingkan nilai lama dan nilai baru untuk bidang apa pun.
G__
24
Jika Karyawan direferensikan oleh baris lain dengan penghapusan cascading, maka baris lainnya masih akan dihapus oleh penggantian.
Don Reba
246
Ini menyakitkan. SQlite membutuhkan UPSERT.
andig
21
Bisakah Anda menjelaskan mengapa ini akan menyisipkan atau mengganti semua kolom dengan nilai baru untuk ID = 1: dianggap BAD dalam contoh pertama Anda? Perintah yang Anda sajikan di sana dimaksudkan untuk membuat catatan baru dengan ID 1, nama John Foo dan CEO , atau menimpa catatan yang ID-nya 1, jika sudah ada di sana, dengan data itu (dengan asumsi id adalah kunci utama) . Jadi, mengapa itu buruk jika hal itu benar-benar terjadi?
ATAU Mapper
10
@Ornelius: Jelas, tapi bukan itu yang terjadi pada contoh pertama. Contoh pertama dimaksudkan untuk secara paksa mengatur semua kolom, yang persis apa yang terjadi, tidak peduli apakah catatan dimasukkan atau diganti. Jadi, mengapa itu dianggap buruk? Jawaban tertaut juga hanya menunjukkan mengapa sesuatu yang buruk dapat terjadi ketika menentukan subset kolom, seperti pada contoh kedua Anda ; tampaknya tidak menguraikan efek buruk apa yang terjadi pada contoh pertama Anda , INSERT OR REPLACEsambil menentukan nilai untuk semua kolom.
ATAU Mapper
134

INSERT OR REPLACE adalah TIDAK setara dengan "UPSERT".

Katakanlah saya memiliki tabel Karyawan dengan id bidang, nama, dan peran:

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

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.

gregschlom
sumber
21
-1 dari saya, saya takut. Ya, jawaban yang diterima salah, tetapi sementara jawaban Anda menunjukkan masalahnya, itu juga bukan jawaban. Untuk jawaban yang sebenarnya, lihat solusi pintar Eric B menggunakan coalesce((select ..), 'new value')klausa tertanam . Menurut saya, jawaban Eric membutuhkan lebih banyak suara.
Hari
22
Memang. Eric jelas merupakan jawaban terbaik dan pantas mendapatkan lebih banyak suara. Yang sedang berkata, saya berpikir bahwa dengan menunjukkan masalah, saya telah berkontribusi sedikit untuk menemukan jawaban yang baik (jawaban Eric datang kemudian, dan dibangun berdasarkan tabel sql sampel dalam jawaban saya). Jadi tidak yakin apakah saya layak mendapatkan -1, tapi tidak apa-apa :)
gregschlom
6
+1 untuk mengimbangi -1 di atas. lol. Timeline menarik di utas ini. Jelas jawaban Anda lebih dari seminggu sebelum jawaban Eric, tetapi Anda berdua menjawab pertanyaan itu hampir dua tahun setelah itu diajukan. +1 untuk Eric juga untuk dielaborasi.
Rich
2
@ QED tidak, karena delete + insert (yang merupakan pengganti) adalah pernyataan 2 dml, dengan pemicu mereka sendiri misalnya. Itu tidak sama dengan 1 pernyataan pembaruan saja.
Sebas
109

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:

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

Catatan khususnya bahwa itu nameadalah kunci alami dari baris - idhanya 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 pada name, saya ingin terus memiliki nilai ID lama (jelas!).

Saya mencapai yang benar UPSERTdengan konstruk berikut:

 WITH new (name, title, author) AS ( VALUES('about', 'About this site', 42) )
 INSERT OR REPLACE INTO page (id, name, title, content, author)
 SELECT old.id, new.name, new.title, old.content, new.author
 FROM new LEFT JOIN page AS old ON new.name = old.name;

Bentuk persis dari kueri ini dapat sedikit berbeda. Kuncinya adalah penggunaan INSERT SELECTdengan gabungan luar kiri, untuk bergabung dengan baris yang ada ke nilai baru.

Di sini, jika suatu baris sebelumnya tidak ada, old.idakan ada NULLdan SQLite kemudian akan menetapkan ID secara otomatis, tetapi jika sudah ada baris seperti itu, old.idakan memiliki nilai aktual dan ini akan digunakan kembali. Itulah tepatnya yang saya inginkan.

Sebenarnya ini sangat fleksibel. Perhatikan bagaimana tskolom tersebut benar-benar hilang di semua sisi - karena memiliki DEFAULTnilai, SQLite hanya akan melakukan hal yang benar, jadi saya tidak perlu mengurusnya sendiri.

Anda juga bisa memasukkan kolom pada kedua newdan oldsisi dan kemudian menggunakan misalnya COALESCE(new.content, old.content)di luar SELECTuntuk mengatakan “insert konten baru jika ada, jika tidak menjaga konten lama” - misalnya jika Anda menggunakan query tetap dan mengikat baru nilai dengan placeholder.

Aristoteles Pagaltzis
sumber
13
+1, bekerja dengan baik, tetapi menambahkan WHERE name = "about"kendala pada SELECT ... AS olduntuk mempercepat. Jika Anda memiliki 1m + baris, ini sangat lambat.
Poin bagus, memberi +1 pada komentar Anda. Saya akan mengabaikannya, karena menambahkan WHEREklausa 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.
Aristoteles Pagaltzis
4
Anda dapat menyederhanakan contoh 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' )
aristotle
4
Tidakkah pemicu yang tidak perlu ini ON DELETEmemicu ketika melakukan penggantian (yaitu, pembaruan)?
Karakuri
3
Ini tentu akan memicu ON DELETEpemicu. 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.)
Aristoteles Pagaltzis
86

Jika Anda umumnya melakukan pembaruan saya akan ..

  1. Mulai transaksi
  2. Lakukan pembaruan
  3. Periksa jumlah baris
  4. Jika 0 lakukan sisipan
  5. Melakukan

Jika Anda umumnya melakukan sisipan saya akan

  1. Mulai transaksi
  2. Coba masukkan
  3. Periksa kesalahan pelanggaran kunci utama
  4. jika kami mendapat kesalahan lakukan pembaruan
  5. Melakukan

Dengan cara ini Anda menghindari pilih dan Anda transaksi suara di Sqlite.

Sam Saffron
sumber
Terima kasih, tanyakan saja ini @ stackoverflow.com/questions/2717590/… . +1 dari saya! =)
Alix Axel
4
Jika Anda akan memeriksa rowcount menggunakan sqlite3_changes () pada langkah ke-3, pastikan Anda tidak menggunakan pegangan DB dari banyak utas untuk modifikasi.
Linulin
3
Bukankah hal berikut ini akan menjadi kurang bertele-tele dengan efek yang sama: 1) pilih tabel bentuk id di mana id = 'x' 2) jika (ResultSet.rows.length == 0) perbarui tabel di mana id = 'x';
Florin
20
Saya benar-benar berharap INSERT OR UPDATE adalah bagian dari bahasa ini
Florin
Ini akhirnya bekerja paling baik untuk saya, mencoba melakukan REPLACE INTO atau selalu dan memasukkan / memperbarui membunuh kinerja saya ketika saya terutama melakukan pembaruan bukannya menyisipkan.
PherricOxide
83

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.

masukkan deskripsi gambar di sini

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.

AnthonyLambert
sumber
5
Amin. Sederhana lebih baik daripada kompleks. Ini terasa sedikit lebih sederhana daripada jawaban yang diterima.
kkurian
4
Saya pikir Anda tidak bisa melakukan INSERT INTO ... WHERE in sqlite? Ini adalah kesalahan sintaks untuk saya di sqlite3
Charlie Martin
5
@CharlieMartin benar. Sintaks ini tidak valid untuk SQLite - itulah yang diminta OP. Sebuah WHEREklausa tidak dapat ditambahkan ke INSERTpernyataan: sqlite-insert ...
colm.anseo
4
Jawaban ini telah membuat saya kehilangan banyak waktu, pertanyaannya adalah tentang SQLITE dan saya tidak tahu bahwa INSERT WHERE tidak didukung dalam sqite, harap tambahkan catatan ini bahwa itu tidak valid untuk sqlite dalam jawaban Anda
Miquel
3
@colminator dan lainnya: Saya memperbaiki pernyataan SQL-nya menggunakan saran Anda.
F Lekschas
62

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:

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

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.

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;
Chris Stavropoulos
sumber
+ 1. Persis apa yang saya coba ganti dengan melakukan konversi dari beberapa kode SQL Server TSQL. Terima kasih. SQL yang saya coba ganti sepertiUpdate <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
DarrenMB
14

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:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

Sisipan uji:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;
David Liebeherr
sumber
2
Ini tampaknya menjadi solusi yang lebih baik bagi saya daripada yang Eric. Namun INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;juga harus bekerja.
bkausbk
Terima kasih, itu juga berfungsi di WebSQL (menggunakan plugin Cordova dan SQLite)
Zappescu
11

Dimulai dengan versi 3.24.0 UPSERT didukung oleh SQLite.

Dari dokumentasi :

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. Sintaks UPSERT telah ditambahkan ke SQLite dengan versi 3.24.0 (tertunda).

UPSERT adalah pernyataan INSERT biasa yang diikuti oleh klausa ON CONFLICT khusus

masukkan deskripsi gambar di sini

Sumber gambar: https://www.sqlite.org/images/syntax/upsert-clause.gif

Lukasz Szozda
sumber
8

Anda memang dapat melakukan upsert dalam SQLite, itu hanya terlihat sedikit berbeda dari yang biasa Anda lakukan. Itu akan terlihat seperti:

INSERT INTO table name (column1, column2) 
VALUES ("value12", "value2") WHERE id = 123 
ON CONFLICT DO UPDATE 
SET column1 = "value1", column2 = "value2" WHERE id = 123
Brill Pappin
sumber
5

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.

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";
Masyarakat
sumber
4

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.

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );
JosephStyons
sumber
7
Cara yang rumit.
frast
Saya pikir itu bukan ide yang baik karena Anda perlu melakukan dua kali permintaan ke mesin database.
Ricardo da Rocha Vitor
3

Jika seseorang ingin membaca solusi saya untuk SQLite di Cordova, saya mendapatkan metode js generik ini karena jawaban @david di atas.

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

Jadi, pertama-tama ambil nama kolom dengan fungsi ini:

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

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

Zappescu
sumber
2

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:

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

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.

Dodzi Dzakuma
sumber
2

Mengikuti Aristoteles Pagaltzis dan ide COALESCEdari 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 ketika INSERTseharusnya dianggap sebagai peningkatan otomatis. Jika itu hanya kunci primer yang dihasilkan maka COALESCEjuga dapat digunakan (lihat komentar Aristoteles Pagaltzis ).

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

Jadi aturan umumnya adalah, jika Anda ingin menyimpan nilai lama, gunakan COALESCE, ketika Anda ingin memperbarui nilai, gunakannew.fieldname

Miquel
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.
Aristoteles Pagaltzis
@AristotlePagaltzis minta maaf jika saya salah, saya tidak menggunakan autoincrements. Yang saya cari dengan permintaan ini adalah untuk memperbarui hanya beberapa cols atau menyisipkan baris penuh dengan nilai yang disediakan jika itu tidak ada . Saya telah bermain dengan kueri Anda dan saya tidak dapat mencapainya saat memasukkan: kolom yang dipilih dari oldtabel tempat ditugaskan NULL, bukan ke nilai yang disediakan new. Inilah alasan untuk menggunakannya COALESCE. 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 autoincrements
Miquel
2
Dalam kasus kunci peningkatan otomatis, Anda ingin memasukkan NULLsebagai kunci, karena itu memberitahu SQLite untuk memasukkan nilai berikutnya yang tersedia.
Aristoteles Pagaltzis
Saya tidak mengatakan Anda tidak boleh melakukan apa yang Anda lakukan (jika Anda membutuhkannya maka Anda membutuhkannya), hanya saja itu bukan jawaban untuk pertanyaan di sini. UPSERT umumnya berarti Anda memiliki baris yang ingin Anda simpan dalam tabel dan tidak tahu apakah Anda sudah memiliki baris yang cocok untuk memasukkan nilai-nilai tersebut atau perlu memasukkannya sebagai baris baru. Kasus penggunaan Anda adalah bahwa jika Anda sudah memiliki baris yang cocok, Anda ingin mengabaikan sebagian besar nilai dari baris baru . Tidak apa-apa, hanya saja bukan pertanyaan yang ditanyakan.
Aristoteles Pagaltzis
1

Saya pikir ini mungkin yang Anda cari: ON CONFLICT klausa .

Jika Anda mendefinisikan tabel Anda seperti ini:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

Sekarang, jika Anda melakukan INSERT dengan id yang sudah ada, SQLite secara otomatis melakukan UPDATE dan bukan INSERT.

Hth ...

kmelvn
sumber
6
Saya tidak berpikir karya ini, akan menghapus kolom hilang dari pernyataan insert
Sam Saffron
3
@ Mosor: -1 dari saya, maaf. Ini sama dengan mengeluarkan REPLACEpernyataan.
Alix Axel
7
-1 karena ini tidak menghapus dan kemudian memasukkan jika kunci utama sudah ada.
frast
1

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.

sapbucket
sumber
Catatan untuk semua orang: INSERT ATAU REPLACE BUKAN tentang apa posting ini. Masukkan atau ganti akan membuat baris baru di tabel Anda, dengan ID baru. Itu bukan PEMBARUAN.
sapbucket
-2

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!

SBB
sumber
21
-1 INSERT OR REPLACEbukan UPSERT. Lihat "jawaban" gregschlom untuk alasan mengapa. Solusi Eric B benar-benar berfungsi dan membutuhkan beberapa perbaikan.
Hari
-4
SELECT COUNT(*) FROM table1 WHERE id = 1;

jika COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

lain jika COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;
mjb
sumber
Ini terlalu rumit, SQL dapat menangani ini dengan baik dalam satu permintaan
Robin Kanters