SQLite UPSERT / GÜNCELLEME VEYA EKLEME


104

Bir SQLite Veritabanına karşı UPSERT / INSERT OR UPDATE yapmam gerekiyor.

INSERT OR REPLACE komutu vardır ki bu birçok durumda yararlı olabilir. Ancak, yabancı anahtarlar nedeniyle otomatik artırma ile kimliğinizi yerinde tutmak istiyorsanız, satırı sildiği, yeni bir tane oluşturduğu ve dolayısıyla bu yeni satırın yeni bir kimliği olduğu için çalışmaz.

Bu tablo olabilir:

oyuncular - (id üzerindeki birincil anahtar, benzersiz kullanıcı_adı)

|  id   | user_name |  age   |
------------------------------
|  1982 |   johnny  |  23    |
|  1983 |   steven  |  29    |
|  1984 |   pepee   |  40    |

Yanıtlar:


53

Bu geç bir cevap. 4 Haziran 2018'de yayınlanan SQLIte 3.24.0'dan başlayarak, PostgreSQL sözdiziminin ardından nihayet UPSERT yan tümcesi için bir destek var .

INSERT INTO players (user_name, age)
  VALUES('steven', 32) 
  ON CONFLICT(user_name) 
  DO UPDATE SET age=excluded.age;

Not: 3.24.0'dan önceki bir SQLite sürümünü kullanmak zorunda olanlar için lütfen aşağıdaki cevaba bakın (benim tarafımdan gönderilmiş, @MarqueIV).

Bununla birlikte, yükseltme seçeneğiniz varsa, benim çözümümün aksine, bunu yapmanız şiddetle tavsiye edilir, burada yayınlanan program istenen davranışı tek bir ifadede elde eder. Ayrıca, genellikle daha yeni bir sürümle birlikte gelen diğer tüm özellikleri, iyileştirmeleri ve hata düzeltmelerini alırsınız.


Şimdilik, Ubuntu deposunda bu sürüm henüz yok.
bl79

Bunu neden android üzerinde kullanamıyorum? Denedim db.execSQL("insert into bla(id,name) values (?,?) on conflict(id) do update set name=?"). "Açık" kelimesinde bana bir sözdizimi hatası veriyor
Bastian Voigt

1
@BastianVoigt Çünkü Android'in çeşitli sürümlerinde kurulu olan SQLite3 kitaplıkları 3.24.0'dan daha eski. Bakınız: developer.android.com/reference/android/database/sqlite/… Ne yazık ki, Android veya iOS'ta yeni bir SQLite3 özelliğine (veya başka bir sistem kitaplığına) ihtiyacınız var, SQLite'nin belirli bir sürümünü kendi yüklü sisteme güvenmek yerine uygulama.
prapin

UPSERT yerine, ilk olarak eki denediğinden bu daha çok bir INDATE değil mi? ;)
Mark A. Donohoe

@BastianVoigt, lütfen 3.24.0'dan önceki sürümler için aşağıdaki cevabıma bakın (yukarıdaki soruda bağlantılı).
Mark A. Donohoe

107

Soru-Cevap Tarzı

Sorunu araştırıp saatlerce tartıştıktan sonra, masanızın yapısına ve bütünlüğü korumak için yabancı anahtar kısıtlamalarının etkinleştirilip etkinleştirilmediğine bağlı olarak bunu başarmanın iki yolu olduğunu öğrendim. Durumumda olabilecek insanlara biraz zaman kazandırmak için bunu temiz bir formatta paylaşmak istiyorum.


1. Seçenek: Satırı silmeyi göze alabilirsiniz

Başka bir deyişle, yabancı anahtarınız yoktur veya varsa, SQLite motorunuz, bütünlük istisnaları olmayacak şekilde yapılandırılmıştır. Gitmenin yolu EKLE VEYA DEĞİŞTİR . Kimliği zaten mevcut olan bir oynatıcıyı eklemeye / güncellemeye çalışıyorsanız, SQLite motoru bu satırı silecek ve sağladığınız verileri ekleyecektir. Şimdi soru geliyor: eski kimliği ilişkilendirmek için ne yapmalı?

User_name = 'steven' ve age = 32 verileriyle UPSERT yapmak istediğimizi varsayalım .

Şu koda bakın:

INSERT INTO players (id, name, age)

VALUES (
    coalesce((select id from players where user_name='steven'),
             (select max(id) from drawings) + 1),
    32)

Hile birleşiyor. Varsa, kullanıcının 'steven' kimliğini döndürür, aksi takdirde yeni bir yeni kimlik döndürür.


2. Seçenek: Satırı silmeyi göze alamazsınız

Önceki çözümle uğraştıktan sonra, benim durumumda, bu kimlik diğer tablo için yabancı anahtar olarak çalıştığından, bunun veriyi yok etmeye neden olabileceğini fark ettim. Ayrıca tabloyu ON DELETE CASCADE cümlesiyle oluşturdum, yani verileri sessizce silecekti. Tehlikeli.

Yani, ilk önce bir IF fıkra düşündü, ama SQLite yalnızca sahiptir ÖRNEĞİ . Ve bu CASE , EXISTS ise (user_name = 'steven' olan oyunculardan id'yi seçin), eğer yoksa INSERT ise bir UPDATE sorgusu gerçekleştirmek için kullanılamaz (veya en azından ben yönetmedim) . Gitme.

Ve sonunda, kaba kuvveti başarıyla kullandım. Mantık, gerçekleştirmek istediğiniz her UPSERT için, öncelikle kullanıcımızla bir satır olduğundan emin olmak için bir INSERT OR IGNORE çalıştırın ve ardından eklemeye çalıştığınız verilerle tam olarak aynı verilerle bir UPDATE sorgusu çalıştırın .

Öncekiyle aynı veriler: user_name = 'steven' ve age = 32.

-- make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32); 

-- make sure it has the right data
UPDATE players SET user_name='steven', age=32 WHERE user_name='steven'; 

Ve hepsi bu!

DÜZENLE

Andy'nin yorumladığı gibi, önce eklemeye ve ardından güncellemeye çalışmak, beklenenden daha sık tetikleyici tetiklemelere neden olabilir. Bu bence bir veri güvenliği sorunu değil, ancak gereksiz olayları ateşlemenin pek mantıklı olmadığı doğrudur. Bu nedenle, iyileştirilmiş bir çözüm şöyle olacaktır:

-- Try to update any existing row
UPDATE players SET age=32 WHERE user_name='steven';

-- Make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32); 

10
Aynen ... 2. seçenek harika. Bunun dışında, tam tersini yaptım: bir güncellemeyi deneyin, rowsAffected> 0 olup olmadığını kontrol edin, yoksa bir ekleme yapın.
Tom Spencer

Bu da oldukça iyi bir yaklaşım, tek küçük dezavantajı, "yükseltme" için tek bir SQL'inizin olmamasıdır.
bgusach

2
son kod örneğindeki güncelleme ifadesinde user_name yeniden ayarlamanıza gerek yoktur. Yaşı ayarlamak yeterlidir.
Serg Stetsuk

72

İşte kaba kuvvetin 'görmezden gelmesini' gerektirmeyen ve yalnızca önemli bir ihlal varsa işe yarayacak bir yaklaşım. Bu yöntem , güncellemede belirttiğiniz tüm koşullara göre çalışır .

Bunu dene...

-- Try to update any existing row
UPDATE players
SET age=32
WHERE user_name='steven';

-- If no update happened (i.e. the row didn't exist) then insert one
INSERT INTO players (user_name, age)
SELECT 'steven', 32
WHERE (Select Changes() = 0);

Nasıl çalışır

'Sihirli sos' burada kullandığı Changes()içinde Wheremaddesi. Changes()son işlemden etkilenen satırların sayısını temsil eder, bu durumda güncellemedir.

Yukarıdaki örnekte, güncellemeden herhangi bir değişiklik yoksa (yani kayıt mevcut değilse) Changes()= 0, böylece Whereifadedeki cümleci Insertdoğru olarak değerlendirilir ve belirtilen verilerle yeni bir satır eklenir.

Eğer Update yaptığımız güncelleştirme varolan satır, daha sonra Changes()1 (daha doğrusu birden fazla satır güncellendi değil sıfır değilse), bu yüzden de 'Nerede' tümcesi = Insertşimdi yanlış olarak değerlendirir ve böylece hiçbir ekleme gerçekleşecek.

Bunun güzelliği, kaba kuvvet gerekmemesi veya gereksiz yere silme ve ardından verileri yeniden girme, bu da yabancı anahtar ilişkilerinde aşağı akış anahtarlarının karışmasına neden olabilir.

Ek olarak, sadece standart bir Wherecümle olduğundan, sadece anahtar ihlallerine değil, tanımladığınız her şeye dayanabilir. Aynı şekilde, Changes()ifadelere izin verilen her yerde istediğiniz / ihtiyaç duyduğunuz her şeyle kombinasyon halinde kullanabilirsiniz .


1
Bu benim için harika çalıştı. Bu çözümü tüm INSERT OR REPLACE örneklerinin yanında başka hiçbir yerde görmedim, kullanım durumum için çok daha esnek.
csab

@MarqueIV ve güncellenmesi veya eklenmesi gereken iki öğe varsa ne olacak? örneğin ilkler güncellendi ve ikincisi mevcut değil. Böyle durumlarda Changes() = 0dönecektir yanlış ve iki satır yapacak INSERT OR REPLACE
Andriy Antonov'u

Genellikle bir UPSERT'nin tek bir kayıt üzerinde hareket etmesi beklenir. Birden fazla kayıt üzerinde çalıştığından emin olduğunuzu söylüyorsanız, sayım kontrolünü buna göre değiştirin.
Mark A. Donohoe

Kötü olan şey, satır varsa, satırın değişip değişmediğine bakılmaksızın güncelleme yönteminin yürütülmesi gerektiğidir.
Jimi

1
Bu neden kötü bir şey? Ve eğer veriler değişmediyse, neden UPSERTen başta arıyorsun? Ancak yine de , güncellemenin gerçekleşmesi, ayarlanması veya ifadenin yanlış bir şekilde tetiklenmesi iyi bir şeydir , ki bunu istemezsiniz. Changes=1INSERT
Mark A. Donohoe

25

Sunulan tüm cevaplarla ilgili sorun, tetikleyicileri (ve muhtemelen diğer yan etkileri) hesaba katmamaktır. Çözüm gibi

INSERT OR IGNORE ...
UPDATE ...

satır mevcut olmadığında her iki tetikleyicinin yürütülmesine (ekleme ve daha sonra güncelleme için) yol açar.

Uygun çözüm

UPDATE OR IGNORE ...
INSERT OR IGNORE ...

bu durumda yalnızca bir ifade çalıştırılır (satır var olduğunda veya olmadığında).


1
Senin değinmek istediğin noktayı anlıyorum. Sorumu güncelleyeceğim. Bu arada, UPDATE OR IGNOREsatır bulunmazsa güncelleme çökmeyeceği için neden gerekli olduğunu bilmiyorum .
bgusach

1
okunabilirlik? Andy'nin kodunun ne yaptığını bir bakışta görebiliyorum. Senin bgusach'ı anlamak için bir dakika çalışmam gerekti.
Brandan

6

Benzersiz ve diğer tuşlara geçiş yapmayan (programcılar için) deliksiz saf bir UPSERT'ye sahip olmak için:

UPDATE players SET user_name="gil", age=32 WHERE user_name='george'; 
SELECT changes();

SELECT değişiklikleri (), son sorgulamada yapılan güncelleme sayısını döndürür. Ardından, değişikliklerden () gelen dönüş değerinin 0 olup olmadığını kontrol edin, öyleyse yürütün:

INSERT INTO players (user_name, age) VALUES ('gil', 32); 

Bu, @fiznool'un yorumunda önerdiği şeye eşdeğerdir (yine de onun çözümü için gidecek olsam da). Sorun değil ve aslında iyi çalışıyor, ancak benzersiz bir SQL deyiminiz yok. UPSERT'nin PK veya diğer benzersiz anahtarlara dayanmaması bana çok az anlam ifade ediyor veya hiç anlamıyor.
bgusach

4

Ayrıca, kullanıcı_adı benzersiz kısıtlamanıza bir ON CONFLICT REPLACE yan tümcesi ekleyebilir ve ardından bir çakışma durumunda ne yapılacağını anlamak için onu SQLite'a bırakarak yalnızca INSERT yapabilirsiniz. Bakınız: https://sqlite.org/lang_conflict.html .

Ayrıca silme tetikleyicileriyle ilgili cümleye dikkat edin: DEĞİŞTİR çakışma çözme stratejisi bir kısıtlamayı yerine getirmek için satırları sildiğinde, silme tetikleyicileri yalnızca ve ancak yinelemeli tetikleyiciler etkinleştirilirse etkinleşir.


2

Seçenek 1: Ekle -> Güncelle

Her iki kaçınmak istiyorsanız changes()=0ve INSERT OR IGNOREsen satırı silmenin göze alamaz bile - bu mantığı kullanabilirsiniz;

Önce (yoksa) ekleyin ve ardından benzersiz anahtarla filtreleyerek güncelleyin .

Misal

-- Table structure
CREATE TABLE players (
    id        INTEGER       PRIMARY KEY AUTOINCREMENT,
    user_name VARCHAR (255) NOT NULL
                            UNIQUE,
    age       INTEGER       NOT NULL
);

-- Insert if NOT exists
INSERT INTO players (user_name, age)
SELECT 'johnny', 20
WHERE NOT EXISTS (SELECT 1 FROM players WHERE user_name='johnny' AND age=20);

-- Update (will affect row, only if found)
-- no point to update user_name to 'johnny' since it's unique, and we filter by it as well
UPDATE players 
SET age=20 
WHERE user_name='johnny';

Tetikleyicilerle ilgili olarak

Uyarı: Hangi tetikleyicilerin çağrıldığını görmek için test etmedim, ancak aşağıdakileri varsayıyorum :

satır yoksa

  • EKLEMEDEN ÖNCE
  • INSTEAD OF kullanarak INSERT
  • EKLE SONRA
  • GÜNCELLEMEDEN ÖNCE
  • INSTEAD OF kullanarak GÜNCELLE
  • GÜNCELLEMEDEN SONRA

satır varsa

  • GÜNCELLEMEDEN ÖNCE
  • INSTEAD OF kullanarak GÜNCELLE
  • GÜNCELLEMEDEN SONRA

2. Seçenek: Ekleme veya değiştirme - kendi kimliğinizi saklayın

bu şekilde tek bir SQL komutuna sahip olabilirsiniz

-- Table structure
CREATE TABLE players (
    id        INTEGER       PRIMARY KEY AUTOINCREMENT,
    user_name VARCHAR (255) NOT NULL
                            UNIQUE,
    age       INTEGER       NOT NULL
);

-- Single command to insert or update
INSERT OR REPLACE INTO players 
(id, user_name, age) 
VALUES ((SELECT id from players WHERE user_name='johnny' AND age=20),
        'johnny',
        20);

Düzenle: seçenek 2 eklendi.

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.