SQLite - UPSERT * değil * INSERT veya REPLACE


535

http://en.wikipedia.org/wiki/Upsert

SQL Server'da depolanan proc güncellemesini ekle

Bunu düşünmediğim SQLite'de yapmanın akıllıca bir yolu var mı?

Temel olarak kayıt varsa dört sütundan üçünü güncellemek istiyorum, yoksa, dördüncü sütun için varsayılan (NUL) değeri ile kayıt INSERT istiyorum.

Kimlik birincil anahtardır, bu nedenle UPSERT için yalnızca bir kayıt olacaktır.

(Ben açıkça UPDATE veya INSERT gerekip gerekmediğini belirlemek için SELECT yükünü önlemek için çalışıyorum)

Öneriler?


SQLite sitesindeki sözdiziminin TABLE CREATE için onaylayamıyorum. Test etmek için bir demo oluşturmadım, ancak desteklenmiyor gibi görünüyor.

Öyleyse, üç sütun var, bu yüzden aslında şöyle görünecektir:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    Blob1 BLOB ON CONFLICT REPLACE, 
    Blob2 BLOB ON CONFLICT REPLACE, 
    Blob3 BLOB 
);

ancak ilk iki leke bir çatışmaya neden olmaz, sadece ID olurdu Blob1 ve Blob2 değiştirilmeyecek


Bağlama verileri tam bir işlem olduğunda SQLite'deki UPDATE'ler, yani güncellenecek her gönderilen satır şunları gerektirir: INSERT'ten farklı olarak reset fonksiyonunun kullanılmasına izin veren ifadeleri Hazırla / Bağla / Adım / Sonlandır

Bir ifade nesnesinin ömrü şöyle bir şeydir:

  1. Sqlite3_prepare_v2 () kullanarak nesneyi oluşturun
  2. Sqlite3_bind_ arabirimlerini kullanarak değerleri host parametrelerine bağlayın.
  3. Sqlite3_step () öğesini çağırarak SQL'i çalıştırın
  4. Sqlite3_reset () öğesini kullanarak ifadeyi sıfırlayın, ardından 2. adıma dönün ve tekrarlayın.
  5. Sqlite3_finalize () öğesini kullanarak deyim nesnesini yok edin.

GÜNCELLEME Ben INSERT ile karşılaştırıldığında yavaş olduğunu, ancak Birincil anahtar kullanarak SELECT ile nasıl karşılaştırılır?

Belki de 4. sütunu (Blob3) okumak için seçin ve sonra ilk 4 sütun için yeni verilerle orijinal 4. Sütun harmanlayan yeni bir kayıt yazmak için REPLACE kullanmalıyım?


5
SQLite - UPSERT mevcut ön sürümde bakın: sqlite.1065341.n5.nabble.com/...
Gaurav

3
UPSERT, SQLite 3.24.0 sürümünde mevcuttur
pablo_worker

Yanıtlar:


854

Tabloda üç sütun varsayarsak: ID, NAME, ROLE


KÖTÜ: Bu, tüm sütunları ID = 1 için yeni değerlerle ekler veya değiştirir:

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

KÖTÜ: Bu, sütunların 2'sini ekler veya değiştirir ... NAME sütunu NULL olarak veya varsayılan değer olarak ayarlanır:

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

İYİ : SQLite SQLite On çakışma maddesi UPSERT desteği kullanın ! UPSERT sözdizimi, SQLite'a 3.24.0 sürümü ile eklendi!

UPSERT, INSERT'in benzersiz bir kısıtlamayı ihlal etmesi durumunda INSERT'in UPDATE veya no-op olarak davranmasına neden olan özel bir sözdizim ekidir. UPSERT standart SQL değildir. SQLite'deki UPSERT, PostgreSQL tarafından oluşturulan sözdizimini takip eder.

resim açıklamasını buraya girin

İYİ ama eğilimli: Bu sütun 2 güncelleme olacak. ID = 1 olduğunda, NAME etkilenmez. ID = 1 mevcut değilse, ad varsayılan (NULL) olur.

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

Bu sütunların 2'sini güncelleyecektir. ID = 1 olduğunda ROLE etkilenmez. ID = 1 mevcut değilse, rol varsayılan değer yerine 'Benchwarmer' olarak ayarlanır.

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

33
+1 mükemmel! Katıştırılmış seçme yan tümcesi, herhangi bir alan için eski değeri ve yeni değeri birleştirmeniz / karşılaştırmanız gerekiyorsa varsayılan ON CONFLICT REPLACE işlevini geçersiz kılma esnekliği sağlar.
G__

24
Çalışan basamaklı silme işlemine sahip diğer satırlar tarafından başvurulursa, diğer satırlar değiştirilmeye devam edilir.
Don Reba

246
Bu acıtıyor. SQlite'ın UPSERT'e ihtiyacı var.
andig

21
Bu işlemin neden tüm sütunları ID = 1 için yeni değerlerle ekleyeceğini veya değiştireceğini açıklayabilir misiniz : ilk örneğinizde BAD olarak kabul edilir mi? Burada sunduğunuz komut, ID 1, John Foo ve rol CEO'su ile yeni bir kayıt oluşturmak veya zaten varsa, bu verilerle kimliği 1 olan kaydın üzerine yazmak anlamına gelir ( kimliğin birincil anahtar olduğu varsayılarak ) . Peki, eğer tam olarak bu olursa neden kötü?
VEYA Haritacı

10
@Cornelius: Bu açık, ama ilk örnekte olan bu değil. İlk örnek, tüm sütunları zorla ayarlamak içindir; bu, kaydın eklenmesine veya değiştirilmesine bakılmaksızın, tam olarak ne olacağıdır. Peki, bu neden kötü sayılıyor? Bağlantılı cevap ayrıca, yalnızca ikinci örneğinizde olduğu gibi, sütunların bir alt kümesini belirtirken neden kötü bir şeyin olabileceğini gösterir ; tüm sütunlar için değerler belirtirken , ilk örneğinizde olanların kötü etkileri üzerinde ayrıntılı olarak durmuyor gibi görünüyor . INSERT OR REPLACE
VEYA Haritacı

134

INSERT VEYA DEĞİŞTİR "UPSERT" ile eşdeğer DEĞİLDİR .

Diyelim ki id, name ve role sahip alanlara sahip tablo var:

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, 1 numaralı çalışanın adını kaybettiniz. SQLite bunu varsayılan bir değerle değiştirdi.

Bir UPSERT'nin beklenen çıktısı rolü değiştirmek ve ismi korumak olacaktır.


21
-1 benden korkuyorum. Evet, kabul edilen cevap yanlış, ancak cevabınız sorunu işaret ederken, bu da bir cevap değil. Gerçek bir cevap için, gömülü bir cümle kullanarak Eric B'nin akıllı çözümüne bakın coalesce((select ..), 'new value'). Eric'in cevabının burada daha fazla oy alması gerektiğini düşünüyorum.
Gün

22
Aslında. Eric kesinlikle en iyi cevap ve daha fazla oyu hak ediyor. Bununla birlikte, sorunu işaret ederek, iyi cevabı bulmaya biraz katkıda bulunduğumu düşünüyorum (Eric'in cevabı daha sonra geldi ve cevabımdaki örnek sql tablolarını temel alıyor). -1, ama nevermind hak ediyorum emin değilim :)
gregschlom

6
Yukarıdaki -1'i dengelemek için +1. lol. Yine de bu iş parçacığında ilginç bir zaman çizelgesi. Açıkçası cevabınız Eric'inkinden bir hafta önceydi, ama ikiniz de soruyu sorulduktan yaklaşık iki yıl sonra cevapladınız. Eric için detaylı olsa da +1.
Zengin


2
@QED no, çünkü delete + insert (bunun yerine geçer) örneğin kendi tetikleyicileriyle birlikte 2 dml ifadesidir. Sadece 1 güncelleme ifadesi ile aynı değildir.
Sebas

109

Mevcut satırdan yalnızca bir veya iki sütun korumak istiyorsanız, Eric B'nin cevabı tamam. Çok sayıda sütunu korumak istiyorsanız, çok hantal bir hal alır.

İşte her iki taraftaki herhangi bir sütuna iyi ölçeklenecek bir yaklaşım. Bunu göstermek için aşağıdaki şemayı alacağım:

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

Özellikle namesatırın doğal anahtarı olduğunu unutmayın - idyalnızca yabancı anahtarlar için kullanılır, bu nedenle nokta SQLite için yeni bir satır eklerken kimlik değerini kendisi seçmektir. Ancak varolan bir satırı güncellerken name, eski kimlik değerine sahip olmaya devam etmesini istiyorum (tabii ki!).

UPSERTAşağıdaki yapı ile bir doğru elde :

 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;

Bu sorgunun tam biçimi biraz değişebilir. Anahtar, INSERT SELECTmevcut bir satırı yeni değerlere birleştirmek için sol dış birleşim ile kullanılmasıdır .

Bir satır daha önce olmasaydı Burada, old.idolacak NULLve SQLite sonra otomatik olarak bir kimlik atayacaktır ama zaten böyle bir satır varsa, old.idgerçek bir değere sahip olacak ve bu yeniden kullanılacaktır. Tam da istediğim bu.

Aslında bu çok esnektir. tsSütunun her tarafta nasıl tamamen eksik olduğuna dikkat edin - bir DEFAULTdeğeri olduğu için, SQLite her durumda doğru olanı yapacak, bu yüzden kendime bakmak zorunda değilim.

Ayrıca hem bir sütun içerebilir newve oldörneğin kullanmak ardından yan ve COALESCE(new.content, old.content)dış içinde SELECTsabit bir sorgu kullanarak ve yeni bağlayıcı eğer örneğin - “Aksi eski içerik tutmak, eğer birisi olduğunu yeni içerik ekleme” demek değerleri.


13
+1, harika çalışıyor, ancak işleri hızlandırmak için WHERE name = "about"kısıtlama ekleyin SELECT ... AS old. 1m + satırınız varsa, bu çok yavaştır.

İyi nokta, yorumunuzda +1. Bunu cevap dışında bırakacağım, çünkü böyle bir WHEREcümle eklemek , bu yaklaşımı bulduğumda ilk etapta yok etmeye çalıştığım sorguda sadece bir tür fazlalık gerektiriyor. Her zaman olduğu gibi: performansa ihtiyacınız olduğunda, denormalize - bu durumda sorgunun yapısı.
Aristoteles Pagaltzis

4
İsterseniz 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'ın

4
Bu, gereksiz bir tetikleyici ON DELETEbir değiştirme (yani bir güncelleme) gerçekleştirdiğinde tetiklenmez mi?
Karakuri

3
Kesinlikle tetikleyicileri ON DELETEtetikleyecektir. Gereksiz yere bilmiyorum. Çoğu kullanıcı için, muhtemelen gereksiz, hatta istenmeyen olabilir, ancak belki tüm kullanıcılar için olmayabilir. Aynı şekilde, yabancı anahtarları olan herhangi bir satırı söz konusu satıra kademeli olarak sileceği için - muhtemelen birçok kullanıcı için bir sorun. SQLite'nin maalesef gerçek bir UPSERT'ye yakın bir şeyi yok. ( INSTEAD OF UPDATESanırım bir tetikleyici ile taklit etmek için
tasarruf edin

86

Genellikle güncellemeler yapıyorsanız ...

  1. Bir işleme başlayın
  2. Güncellemeyi yap
  3. Satır sayısını kontrol edin
  4. 0 ise eki yapın
  5. işlemek

Genellikle kesici uçlar yapıyorsanız

  1. Bir işleme başlayın
  2. Bir insert deneyin
  3. Birincil anahtar ihlali hatasını kontrol edin
  4. bir hata alırsak güncellemeyi yap
  5. işlemek

Bu şekilde seçimden kaçınırsınız ve işlemsel olarak Sqlite'da ses çıkarırsınız.


Teşekkürler, bu soruyu @ stackoverflow.com/questions/2717590/… . Benden +1! =)
Alix Axel

4
Satır sayısını 3. adımda sqlite3_changes () kullanarak kontrol edecekseniz, değişiklikler için birden çok iş parçacığından DB tanıtıcısını kullanmadığınızdan emin olun.
Linulin

3
Aşağıdakiler aynı etkiyle henüz daha az garip olmaz: 1) id = 'x' 2) id form tablosunu seçin, eğer (ResultSet.rows.length == 0) id = 'x' güncelleme tablosunda;
Florin

20
Keşke INSERT VEYA GÜNCELLEME dilin bir parçası olsaydı
Florin

Bu benim için en iyi şekilde sonuçlandı, INTO DEĞİŞTİR ya da her zaman yapmaya çalıştı ve insert / update, insertler yerine öncelikle güncellemeler yaptığımda performansımı öldürüyordu.
PherricOxide

83

Bu yanıt güncellendi ve bu nedenle aşağıdaki yorumlar artık geçerli değil.

2018-05-18 BASIN DURDUR.

SQLite'de UPSERT desteği! UPSERT sözdizimi, sürüm 3.24.0 (beklemede) olan SQLite'a eklendi!

UPSERT, INSERT'in benzersiz bir kısıtlamayı ihlal etmesi durumunda INSERT'in UPDATE veya no-op olarak davranmasına neden olan özel bir sözdizim ekidir. UPSERT standart SQL değildir. SQLite'deki UPSERT, PostgreSQL tarafından oluşturulan sözdizimini takip eder.

resim açıklamasını buraya girin

alternatif olarak:

Bunu yapmanın tamamen farklı bir yolu: Benim uygulamada bellek rowID uzun olarak ayarlayın.MaxValue bellekte satır oluşturduğunuzda. (MaxValue hiçbir zaman bir kimlik olarak kullanılmayacak, yeterince uzun yaşayamayacaksınız .... Sonra rowID bu değer değilse, zaten veritabanında olması gerekir, bu nedenle MaxValue ise bir UPDATE gerekir sonra bir ek gerekir. Bu, yalnızca uygulamanızdaki rowID'leri izleyebiliyorsanız faydalıdır.


5
Amin. Basit, karmaşık olmaktan iyidir. Bu, kabul edilen cevaptan biraz daha basit geliyor.
kkurian

4
Ben içeriye sokamadığınızı sanıyordum ... sqlite NEREDE? Bu sqlite3 benim için bir sözdizimi hatası
Charlie Martin

5
@CharlieMartin doğru. Bu sözdizimi, OP'nin istediği SQLite için geçersizdir. Bir WHEREcümle INSERTifadeye
eklenemez

4
Bu cevap beni çok fazla zaman kaybettirdi, soru SQLITE ile ilgili ve INSERT NEREDE'nin sqite'de desteklenmediğini bilmiyordum, lütfen cevabınızdaki sqlite için geçersiz olduğunu unutmayın
Miquel

3
@colminator ve diğerleri: SQL ifadesini önerinizi kullanarak düzelttim.
F Lekschas

62

Bu eski bir iş parçacığı olduğunu fark ama geç sqlite3 çalışıyorum ve dinamik olarak parametreli sorgular üreten benim ihtiyaçlarına daha uygun olan bu yöntem ile geldi:

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

Hala güncelleme üzerinde bir nerede yan tümcesi ile 2 sorgu ama hile yapmak gibi görünüyor. Ayrıca değişikliklere () çağrı sıfırdan büyükse sqlite güncelleme deyimini tamamen optimize edebilir kafamda bu vizyon var. Aslında bu benim bilgim dışında mı, değil mi ama bir adam hayal edebilir mi? ;)

Bonus puanlar için, yeni eklenen bir satır veya mevcut bir satır olsun, satır kimliğini döndüren bu satırı ekleyebilirsiniz.

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

+ 1. Tam olarak ne bazı SQL Server TSQL kodundan bir dönüştürme yaparak değiştirmeye çalışıyordum. Teşekkürler. Değiştirmeye çalıştığım SQL gibiydiUpdate <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
DarrenMB

14

İşte bir INSERT OR REPLACE (birçok durumda farklı çalışır) yerine gerçekten bir UPSERT (UPDATE veya INSERT) olan bir çözüm.

Şu şekilde çalışır:
1. Aynı kimliğe sahip bir kayıt varsa güncellemeyi deneyin.
2. Güncelleme herhangi bir satırı değiştirmediyse ( NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)), kaydı ekleyin.

Böylece mevcut bir kayıt güncellendi veya bir ekleme gerçekleştirilecek.

Önemli ayrıntı, update ifadesinin varolan herhangi bir kayda çarpıp çarpmadığını kontrol etmek ve sadece herhangi bir kayda çarpmadığı takdirde insert ifadesini gerçekleştirmek için changes () SQL işlevini kullanmaktır.

Belirtilmesi gereken bir şey, change () işlevinin alt düzey tetikleyiciler tarafından gerçekleştirilen değişiklikleri döndürmemesi (bkz. Http://sqlite.org/lang_corefunc.html#changes ), bu yüzden bunu dikkate aldığınızdan emin olun.

İşte SQL ...

Test güncellemesi:

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

Test eki:

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

2
Bu benim için Eric olanlardan daha iyi bir çözüm gibi görünüyor. Ancak INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;aynı zamanda çalışmalıdır.
bkausbk

Teşekkürler dostum, aynı zamanda WebSQL'de de çalışıyor (Cordova ve SQLite eklentisini kullanarak)
Zappescu

11

3.24.0 sürümünden başlayarak UPSERT, SQLite tarafından desteklenmektedir.

Gönderen belgeler :

UPSERT, INSERT'in benzersiz bir kısıtlamayı ihlal etmesi durumunda INSERT'in UPDATE veya no-op olarak davranmasına neden olan özel bir sözdizim ekidir. UPSERT standart SQL değildir. SQLite'deki UPSERT, PostgreSQL tarafından oluşturulan sözdizimini takip eder. UPSERT sözdizimi, sürüm 3.24.0 (beklemede) olan SQLite'a eklendi.

UPSERT, özel bir ON CONFLICT deyimi tarafından takip edilen sıradan bir INSERT deyimidir

resim açıklamasını buraya girin

Görüntü kaynağı: https://www.sqlite.org/images/syntax/upsert-clause.gif


8

Gerçekten SQLite bir upsert yapabilirsiniz, sadece alıştığınızdan biraz farklı görünüyor. Şöyle bir şey olurdu:

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

5

Aristoteles'in cevabını genişletmek sahte bir 'singleton' tablosundan (tek sıralı kendi yarattığınız bir tablo) SELECT yapabilirsiniz. Bu, bazı tekrarlamaları önler.

Ayrıca örnek taşınabilir MySQL ve SQLite tuttu ve nasıl bir sütun sadece ilk kez ayarlamak bir örnek olarak 'date_added' sütun kullandım.

 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";

4

Bildiğim en iyi yaklaşım bir güncelleme ve ardından bir ekleme yapmaktır. "Bir seçimin ek yükü" gereklidir, ancak hızlı olan birincil anahtarı aradığınız için bu korkunç bir yük değildir.

İstediğinizi yapmak için aşağıdaki ifadeleri tablo ve alan adlarınızla değiştirebilmeniz gerekir.

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

7
Karmaşık bir yol.
frast

Bence bu iyi bir fikir değil çünkü veritabanı motoruna iki kez istek yapmanız gerekiyor.
Ricardo da Rocha Vitor

3

Birisi Cordova'da SQLite için çözümümü okumak istiyorsa, yukarıdaki @david cevabı sayesinde bu genel js yöntemini aldım.

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");
        });
    });
});
}

Bu nedenle, ilk önce bu işlevle sütun adlarını alın:

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);
    });
});
}

Ardından işlemleri programlı olarak oluşturun.

"Değerler", daha önce oluşturmanız gereken bir dizidir ve tabloya eklemek veya tabloya güncellemek istediğiniz satırları temsil eder.

"remoteid", uzak sunucumla senkronize ettiğim için referans olarak kullandığım kimlik.

SQLite Cordova eklentisinin kullanımı için lütfen resmi bağlantıya bakın


2

Bu yöntem, diğer soruların birkaçını bu soruya verilen yanıttan remiks eder ve CTE (Ortak Tablo İfadeleri) kullanımını içerir. Sorguyu tanıtacağım ve neden yaptığımı açıklayacağım.

300 çalışanı varsa 300 çalışanının soyadını DAVIS olarak değiştirmek istiyorum. Aksi takdirde yeni bir çalışan ekleyeceğim.

Tablo Adı: çalışanlar Sütunlar: id, ad_adı, soyadı

Sorgu:

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
;

Temel olarak, CTE'yi, select deyiminin varsayılan değerleri belirlemek için kaç kez kullanılması gerektiğini azaltmak için kullandım. Bu bir CTE olduğundan, tablodan istediğimiz sütunları seçiyoruz ve INSERT deyimi bunu kullanıyor.

Şimdi, COALESCE işlevindeki null değerlerini değiştirerek hangi varsayılan değerleri kullanmak istediğinize değerlerin ne olması gerektiğine karar verebilirsiniz.


2

Aşağıdaki Aristoteles Pagaltzis ve fikrini COALESCEgelen Eric B'nin cevap , burada sadece birkaç sütun güncellemek ya da yoksa tam satır eklemek için bir Upsert seçenektir.

Bu durumda, başlık ve içeriğin güncellenmesi gerektiğini ve mevcut olduğunda diğer eski değerleri koruduğunu ve ad bulunamadığında sağlanan değerleri eklediğini düşünün:

NOT id , otomatik artma olması gerektiğinde NULL değerine zorlanır INSERT. Sadece oluşturulan bir birincil anahtar ise, o COALESCEzaman da kullanılabilir (bkz. Aristoteles Pagaltzis yorumu ).

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;

Bu nedenle genel kural, eski değerleri korumak COALESCEistiyorsanız, değerleri güncellemek istediğinizde şunu kullanın:new.fieldname


COALESCE(old.id, new.id)otomatik yineleme anahtarı ile kesinlikle yanlıştır. Ve birisinin sahip olabileceği bir kullanım durumu gibi “değerlerin eksik olduğu durumlar dışında, satırın çoğunu değiştirmeden kal” gibi görünse de, insanların nasıl UPSERT yaptıklarını aradıklarını sanmıyorum.
Aristoteles Pagaltzis

@AristotlePagaltzis eğer yanılıyorsam özür dilerim, otomatik eklemeler kullanmıyorum. Ne bu sorgu ile aradığım sadece birkaç cols güncelleştirmek veya mevcut değilse verilen değerlerle tam satır eklemek için . Sorgunuzla oynadım ve eklerken başaramadım: verilen değerlere değil, oldatanan tablodan seçilen sütunlar . Kullanım nedeni budur . Ben sqlite konusunda uzman değilim, bu sorguyu test ediyorum ve dava için çalışıyor gibi görünüyor, eğer beni autoincrements ile çözüme yönlendirebilirseniz çok teşekkür ederimNULLnewCOALESCE
Miquel

2
Otomatik artan anahtar durumunda, anahtar olarak eklemek istersinizNULL , çünkü bu SQLite'a bir sonraki kullanılabilir değeri eklemesini söyler.
Aristoteles Pagaltzis

Yaptığınız şeyi yapmamanız gerektiğini söylemiyorum (eğer ihtiyacınız varsa ona ihtiyacınız var), sadece buradaki sorunun cevabı değil. UPSERT genellikle tabloda depolanmasını istediğiniz bir satırınız olduğu anlamına gelir ve sadece değerleri koymak için eşleşen bir satırınız olup olmadığını bilmiyorsanız veya bunları yeni bir satır olarak eklemeniz gerekir. Kullanım durumunuz, eşleşen bir satırınız varsa , yeni satırdaki değerlerin çoğunu yoksaymak istediğinizdir . Bu iyi, sadece sorulan soru değil.
Aristoteles Pagaltzis

1

Aradığın şey bu olabilir: ON CONFLICT yan tümcesi .

Tablonuzu şu şekilde tanımlarsanız:

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

Şimdi, zaten var olan bir kimliğe sahip bir INSERT yaparsanız, SQLite otomatik olarak INSERT yerine UPDATE yapar.

Hth ...


6
Bunun işe yaradığını düşünmüyorum, insert deyiminde eksik olan sütunları silecek
Sam Saffron

3
@Mosor: benden -1, üzgünüm. Bu, REPLACEifade vermekle aynı şeydir .
Alix Axel

7
-1 çünkü birincil anahtar zaten varsa bir silme ve daha sonra bir ekleme yapar.
frast

1

Bunu iki operasyonda yapmanın sakıncası yoksa.

Adımlar:

1) "INSERT OR IGNORE" ile yeni öğeler ekleyin

2) Mevcut öğeleri "UPDATE" ile güncelleyin

Her iki adımın da girişi, yeni veya güncellenebilir öğelerle aynı koleksiyondur. Değişiklik gerektirmeyen mevcut öğelerle sorunsuz çalışır. Bunlar güncellenecektir, ancak aynı verilerle ve bu nedenle net sonuç hiçbir değişiklik değildir.

Tabii, daha yavaş, vs. Verimsiz. Evet.

Sql yazmak ve korumak ve anlamak kolay? Kesinlikle.

Dikkate alınması gereken bir değiş tokuş. Küçük upserts için harika çalışıyor. Kod bakımı için verimlilikten ödün vermeyenler için harika çalışıyor.


Herkese not: INSERT VEYA DEĞİŞTİR bu yazının ne hakkında olduğunu DEĞİL. INSERT OR REPLACE tablonuzda yeni bir kimliğe sahip YENİ bir satır oluşturur. Bu bir GÜNCELLEME değil.
sapbucket

-2

Sadece bu konu okumak ve sadece bu "UPSERT" ing için kolay olmadığını hayal kırıklığına uğradım, ben daha ayrıntılı araştırma ...

Aslında bunu doğrudan ve kolayca SQLITE içinde yapabilirsiniz.

Kullanmak yerine: INSERT INTO

kullanın: INSERT OR REPLACE INTO

Bu tam olarak ne yapmak istediğinizi yapar!


21
-1 INSERT OR REPLACEbir değil UPSERT. Nedeni için gregschlom'un "cevabına" bakınız . Eric B'nin çözümü aslında çalışıyor ve bazı upvotes gerekiyor.
Gün

-4
SELECT COUNT(*) FROM table1 WHERE id = 1;

Eğer COUNT(*) = 0

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

başka eğer COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;

Bu çok karmaşık, SQL tek bir sorguda bu iyi işleyebilir
Robin Kanters
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.