Varsa güncelle yoksa ekle: SQL’de upsert yazmanın yolları
Beceri rehberi

Varsa güncelle yoksa ekle: SQL’de upsert yazmanın yolları

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 7 dk okuma

Upsert, kaydı önce kontrol etmeden tek komutta ekleyen ya da güncelleyen işlemdir. SQL Server, PostgreSQL ve MySQL’de söz dizimi, örnek ve tuzaklarıyla anlatılır.

Bir bakışta

  1. Upsert, kayıt varsa güncelleyen yoksa ekleyen işlemdir. SQL Server MERGE ile, PostgreSQL ON CONFLICT ile, MySQL ON DUPLICATE KEY UPDATE ile yazar.
  2. Eşleşmeyi belirleyen sütunda benzersizlik güvencesi şarttır. Anahtar tanımsızsa hiçbir yöntem aynı kaydı güvenilir tanıyamaz.
  3. MERGE'de kaynakta mükerrer anahtar olması hata üretir. Kaynağı önce tekilleştirin, ON koşuluna süzme koymayın.
  4. Silme yan tümcesi en pahalı hatadır. Yarım yüklenmiş bir kaynakla WHEN NOT MATCHED BY SOURCE doğru satırları siler.
  5. Üretime almadan önce boş, mükerrer ve zaten güncel kaynak senaryoları denenmelidir. Yoğun eşzamanlılıkta ayrı komutlar MERGE'den iyi çalışabilir.

Upsert nedir, neden tek komut istenir?

Upsert, "update" ile "insert" sözcüklerinin birleşimidir: gelen kayıt tabloda zaten varsa güncellenir, yoksa yeni satır olarak eklenir. Her gün gelen bir müşteri listesini ana tabloya işlemek, bir kaynak sistemden alınan ürün bilgilerini güncel tutmak ya da bir uygulamanın "kaydet" düğmesini hem yeni hem mevcut kayıt için çalıştırmak tipik örneklerdir.

Bunu ayrı komutlarla yapmak mümkündür: önce kaydın var olup olmadığını SELECT ile sorarsınız, sonra duruma göre UPDATE ya da INSERT çalıştırırsınız. Sorun, kontrol ile yazma arasında geçen sürede başka bir oturumun aynı kaydı eklemesidir. Oturumların hepsi "yok" cevabını alır, hepsi ekler ve birincil anahtar çakışması çıkar. Tek komutla yapılan upsert bu aralığı kapatmayı ve kodu kısaltmayı hedefler.

Standart SQL'de upsert için MERGE adlı bir komut vardır ve SQL Server bunu destekler. PostgreSQL ve MySQL ise aynı işi INSERT komutuna eklenen özel bir yan tümceyle yapar. Bu yüzden "upsert" tek bir söz dizimi değil, bir ihtiyaçtır; hangi sistemde çalıştığınız yazacağınız komutu belirler.

MERGE komutunun iskeleti

SQL Server'daki MERGE, bir hedef tabloyu bir kaynakla eşleştirir ve eşleşme durumuna göre ekleme, güncelleme ya da silme yapar. Birkaç parçası vardır: hangi tabloya yazılacağı (MERGE INTO), neyle karşılaştırılacağı (USING), eşleşme koşulu (ON) ve eşleşme sonucuna göre yapılacak işler (WHEN yan tümceleri).

  • WHEN MATCHED: kaynakta ve hedefte aynı anahtara sahip satır var; genellikle UPDATE yazılır.
  • WHEN NOT MATCHED BY TARGET: kaynakta var, hedefte yok; INSERT yazılır.
  • WHEN NOT MATCHED BY SOURCE: hedefte var, kaynakta yok; genellikle DELETE ya da "pasif" işaretleyen UPDATE yazılır.

Kaynak, başka bir tablo olabildiği gibi tek satırlık bir SELECT de olabilir. Uygulamadan gelen tek kaydı işlemek için parametrelerden oluşan bir SELECT yeterlidir:

MERGE INTO dbo.Musteri AS hedef USING (SELECT @musteri_kodu AS musteri_kodu, @ad AS ad, @sehir AS sehir) AS kaynak ON hedef.musteri_kodu = kaynak.musteri_kodu WHEN MATCHED THEN UPDATE SET hedef.ad = kaynak.ad, hedef.sehir = kaynak.sehir WHEN NOT MATCHED BY TARGET THEN INSERT (musteri_kodu, ad, sehir) VALUES (kaynak.musteri_kodu, kaynak.ad, kaynak.sehir) OUTPUT $action, inserted.musteri_kodu;

Komutun sonundaki noktalı virgül isteğe bağlı değildir; Microsoft belgelerine göre MERGE'ün bitişinde bulunması gerekir, yoksa hata alırsınız. OUTPUT satırındaki $action, her satır için yapılan işi (INSERT, UPDATE ya da DELETE) döndürür ve belgelerde etkilenen satırları görmenin önerilen yolu olarak geçer.

Şekil 1

Upsert yöntemi çalıştığınız sisteme göre seçilir

Kayıt varsa güncelle, yoksa ekle işini hangi komutla yazmalı?
  1. SQL Server kullanıyorumMERGEEşleşme koşulu ON ile kurulur, noktalı virgül şarttır.
  2. PostgreSQL kullanıyorumINSERT ... ON CONFLICT DO UPDATEÇakışma hedefi olarak benzersiz bir sütun ya da kısıt gerekir.
  3. MySQL kullanıyorumINSERT ... ON DUPLICATE KEY UPDATEBirincil anahtar ya da benzersiz indeks çakışması tetikler.
  4. Yoğun eşzamanlı yazma varAyrı INSERT ve UPDATE, transaction içindeBelgeler bu durumda ayrı komutların daha az bloklama yapabileceğini söyler.
Kaynak: Kaynak: Microsoft Learn MERGE, PostgreSQL INSERT ve MySQL INSERT ON DUPLICATE KEY belgeleri.Mindset Institute

Örnek: günlük müşteri listesini ana tabloya işlemek

Bir satış ekibinin her sabah CRM'den bir müşteri listesi aldığını ve bunu raporlama veritabanındaki Musteri tablosuna işlediğini düşünün. Liste, önce geçici bir tabloya (örneğin stg_Musteri) yüklenir. Amaç: listede olup tabloda olmayan müşteriler eklensin, ikisinde de olanların ad ve şehir bilgisi güncellensin.

MERGE INTO dbo.Musteri AS hedef USING dbo.stg_Musteri AS kaynak ON hedef.musteri_kodu = kaynak.musteri_kodu WHEN MATCHED AND (hedef.ad <> kaynak.ad OR hedef.sehir <> kaynak.sehir) THEN UPDATE SET hedef.ad = kaynak.ad, hedef.sehir = kaynak.sehir WHEN NOT MATCHED BY TARGET THEN INSERT (musteri_kodu, ad, sehir) VALUES (kaynak.musteri_kodu, kaynak.ad, kaynak.sehir) OUTPUT $action, inserted.musteri_kodu, deleted.ad AS eski_ad;

İlk WHEN yan tümcesine eklenen koşul, bilgisi zaten aynı olan satırlara gereksiz yazma yapmayı önler. Karşılaştırmada NULL değerlerin <> işleciyle beklendiği gibi davranmadığını unutmayın; şehir boş olabiliyorsa koşulu ISNULL ya da EXCEPT ile güçlendirmeniz gerekir. OUTPUT satırı, güncellemelerde eski adı da döndürdüğü için değişikliğin denetim kaydını tutmanıza yarar.

Burada WHEN NOT MATCHED BY SOURCE yan tümcesi bilerek yazılmadı. Listede o gün bulunmayan müşteriyi silmek, günlük listenin tam liste mi yoksa yalnızca değişenler mi olduğunu bilmeden yapılabilecek en pahalı hatadır. Tam liste olduğundan eminseniz bile silmek yerine bir "aktif" bayrağını kapatmak geri dönüşü kolay bir tercihtir.

PostgreSQL ve MySQL'de aynı iş nasıl yazılır?

PostgreSQL'de upsert, INSERT komutuna eklenen ON CONFLICT yan tümcesiyle yazılır. Çakışma durumunda ne olacağını şu seçenekler belirler: DO NOTHING çakışan satırı sessizce atlar, DO UPDATE ise mevcut satırı günceller. DO UPDATE kullanırken çakışmanın hangi sütun ya da kısıta göre aranacağını belirtmek zorunludur ve bu sütunlarda benzersiz bir indeks ya da kısıt bulunmalıdır. Eklenmek istenen yeni değerlere EXCLUDED adlı özel tablodan ulaşılır:

INSERT INTO musteri (musteri_kodu, ad, sehir) VALUES (@musteri_kodu, @ad, @sehir) ON CONFLICT (musteri_kodu) DO UPDATE SET ad = EXCLUDED.ad, sehir = EXCLUDED.sehir RETURNING musteri_kodu;

MySQL'de karşılığı INSERT ... ON DUPLICATE KEY UPDATE'dir. Eklenecek satır birincil anahtar ya da benzersiz bir indeksle çakışırsa satır eklenmez, mevcut satır güncellenir. Dikkat edilecek nokta, tabloda birden çok benzersiz indeks varsa hangisinin çakışacağının belirsizleşmesidir; MySQL belgeleri böyle tablolarda bu yan tümceden kaçınılmasını öneriyor. Yeni değerlere eskiden VALUES() işleviyle ulaşılırdı, belgeler bunu kullanımdan kalkmış olarak işaretliyor ve satıra takma ad vermeyi öneriyor.

Bu sistemlerin ortak şartı net: eşleşmeyi belirleyen sütunlarda benzersizlik güvencesi olmalıdır. Anahtar tanımlı değilse hiçbir upsert yöntemi "aynı kayıt"ı güvenilir biçimde tanıyamaz.

MERGE'de sık yapılan hatalar

Birincisi, kaynakta aynı anahtardan birden çok satır bulunmasıdır. UPDATE içeren bir MERGE'de hedefteki tek bir satır kaynaktaki birden çok satırla eşleşirse SQL Server hata verir; komut aynı satırı tekrar tekrar güncellemez. Günlük listede mükerrer müşteri kodu varsa önce kaynağı tekilleştirin.

İkincisi, ON koşuluna hedef tabloyu daraltan filtreler eklemektir. Belgeler, performansı artırmak için hedef tablodaki satırları ON içinde süzmeye çalışmanın beklenmedik ve yanlış sonuçlara yol açabileceğini belirtir. ON yalnızca eşleştirme sütunlarını içermeli, süzme işi WHEN yan tümcelerindeki ek koşullara ya da kaynak sorgusuna bırakılmalıdır.

Üçüncüsü, eşzamanlılığı hesaba katmamaktır. Aynı anda birden çok oturum aynı anahtarı eklemeye çalışırsa benzersiz anahtar ihlali çıkabilir. Belgeler, anahtarların hem eklenip hem güncellendiği durumlarda HOLDLOCK ipucunun bu ihlali önleyebildiğini, ama eşzamanlılığı azalttığını söyler. Aynı belge, büyük ölçekte MERGE'ün karmaşık eşzamanlılık sorunları doğurabileceğini ve üretime almadan önce iyice denenmesi gerektiğini de vurgular.

Dördüncüsü, WHEN NOT MATCHED BY SOURCE ile istemeden silmektir. Kaynak yarım yüklenmişse hedefteki doğru satırlar silinir. Bu yan tümceyi yazmadan önce kaynağın tam olduğunu doğrulayan bir kontrol (beklenen satır sayısı, dosyanın tamamlandığını gösteren işaret) ekleyin.

Beşincisi, noktalı virgülü unutmaktır; en basit hata, en sık alınan hatalardandır.

Güvenle çalıştırmak: önizleme, OUTPUT ve transaction

Upsert komutları bir anda çok satırı değiştirebildiği için UPDATE ve DELETE'teki güvenlik alışkanlıkları burada da geçerlidir. Önce MERGE'ün kaynak sorgusunu SELECT olarak çalıştırıp beklediğiniz kayıtları görün. Ardından hangi satırların eklenip hangilerinin güncelleneceğini anlamak için kaynakla hedefi LEFT JOIN ile karşılaştıran bir önizleme sorgusu yazın; eşleşmeyen kaynak satırları eklenecekler, eşleşenler güncellenecekler listesidir.

Komutu bir transaction içinde çalıştırıp OUTPUT sonucunu ve satır dağılımını inceledikten sonra COMMIT yapmak, hatayı geri alınabilir tutar. OUTPUT çıktısını bir denetim tablosuna yazmak (OUTPUT ... INTO) hangi kaydın hangi değerden hangisine döndüğünü kalıcı kılar.

Test ortamında şu senaryoları mutlaka deneyin: kaynağın boş olduğu gün, kaynakta mükerrer anahtar bulunan gün, tüm kayıtların zaten güncel olduğu gün ve yalnızca yeni kayıt geldiği gün. Bu durumlar, hatalı MERGE'lerin büyük bölümünü ortaya çıkarır.

Performans için kaynak ve hedefteki eşleştirme sütunlarında indeks bulunmalıdır; belgeler bu indekslerin mümkünse benzersiz olmasını önerir. İndeks yoksa her çalıştırmada tablolar baştan sona taranabilir.

Nereden başlanır, Mindset programlarında nerede çalışılır?

Sırayla ilerlemek en az hata yaptırır: önce SELECT, WHERE ve JOIN, ardından INSERT, UPDATE ve DELETE, sonra transaction, en sonda MERGE ve eşdeğerleri. MERGE bu temellerin birleşimidir; birini bilmeden yazılan MERGE okunmaz ve denetlenemez.

Mindset kataloğunda SQL ile Programlama Eğitimi, Tablolarla Çalışma ve Veri İşleme Dili modülleriyle satır ekleme, güncelleme ve silme temelini kapsar; Join Kullanımı modülü de eşleştirme mantığını pekiştirir. Bu içerik, veri yüklemesi yapan raporlama ve operasyon ekiplerinin MERGE gibi birleşik komutlara geçmeden önce oturtması gereken zemindir.

İlgili Mindset programı

SQL ile Programlama Eğitimi

Sık sorulanlar

Sık sorulan sorular

MERGE yerine neden ayrı UPDATE ve INSERT yazmak bazen daha iyidir?

Microsoft belgeleri, bazı uygulamalarda ayrı INSERT, UPDATE ve DELETE komutlarının daha verimli olabileceğini ve büyük ölçekte MERGE'ün karmaşık eşzamanlılık sorunları getirebileceğini söyler. Yoğun eşzamanlı yazma varsa ayrı komutlar daha az bloklama yapabilir. Yine de tek seferlik yüklemelerde MERGE okunaklı ve kısa bir çözümdür; seçimi sistem yükünüz ve test sonucunuz belirlemelidir.

Upsert yaparken hangi satırların eklendiğini nasıl görürüm?

SQL Server'da OUTPUT yan tümcesine $action eklediğinizde her satır için INSERT, UPDATE ya da DELETE bilgisi döner. PostgreSQL'de RETURNING eklenen veya güncellenen satırları verir; ancak koşulu sağlanmayıp güncellenmeyen satırlar sonuçta görünmez. MySQL'de etkilenen satır sayısı eklemeyle güncellemeyi farklı raporlar, bu yüzden sonucu ayrıca sorguyla doğrulamak daha güvenlidir.

MERGE komutu neden hata verip benzersiz anahtar ihlali bildiriyor?

En olası neden eşzamanlılıktır: birden çok oturum aynı anda aynı anahtarı eklemeye çalışmıştır. Belgeler, anahtarların hem eklendiği hem güncellendiği senaryolarda HOLDLOCK ipucunun ihlali önleyebildiğini belirtir; bunun bedeli eşzamanlılığın düşmesidir. Kaynakta aynı anahtardan mükerrer satır olması da benzer bir hata üretir, bu yüzden kaynağı önce tekilleştirin.

Boş değerler (NULL) upsert karşılaştırmasını nasıl etkiler?

Eşleştirmede kullandığınız anahtar sütun NULL içeriyorsa, NULL ile NULL eşit sayılmadığı için satır hiçbir zaman eşleşmez ve her çalıştırmada yeniden eklenir. Güncelleme koşulunda değişiklik denetlerken de aynı tuzak vardır: boş değerler farklı görünmez ama biri boşken diğeri dolu olan satır <> ile yakalanmayabilir. Anahtarları boş bırakmayın, karşılaştırmada ISNULL ya da EXCEPT kullanın.

PostgreSQL'de DO NOTHING ile DO UPDATE arasında nasıl seçim yapılır?

Mevcut kaydın korunması gerekiyorsa DO NOTHING uygundur; örneğin aynı olayı yinelenen bir mesaj yüzünden kaydetmek istemediğinizde. Yeni bilginin eskisini ezmesi gerekiyorsa DO UPDATE kullanılır. DO UPDATE için çakışma hedefi belirtmek zorunludur; DO NOTHING için isteğe bağlıdır ve verilmezse tüm benzersiz kısıtlar dikkate alınır.

SQL MERGE ile Upsert: Varsa Güncelle, Yoksa Ekle