SQL Cursor (İmleç) Nasıl Yazılır? Satır Satır İşlem ve Küme Tabanlı Alternatifler
Beceri rehberi

SQL Cursor (İmleç) Nasıl Yazılır? Satır Satır İşlem ve Küme Tabanlı Alternatifler

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

SQL cursor, bir sorgu sonucunu satır satır dolaşıp her satır için ayrı işlem yapmanızı sağlar. Burada konu veritabanı imlecidir; Cursor adlı kod düzenleyicisiyle ilgisi yoktur.

Bir bakışta

  1. Cursor, sonucu bir seferde değil satır satır işler. Her satırı değişkenlere alıp tek tek işlem yaparsınız; SQL'in doğal çalışma biçimi ise kümelerdir.
  2. Yaşam döngüsü sabittir. DECLARE, OPEN, FETCH NEXT, döngü, CLOSE ve DEALLOCATE sırası bozulursa kaynak sızar ya da döngü bitmez.
  3. Önce küme tabanlı çözümü deneyin. UPDATE, JOIN, toplulaştırma ve pencere fonksiyonları çoğu satır satır döngünün yerine geçer.
  4. Cursor, satır başına bir yordam ya da dış çağrı gerektiğinde haklıdır. Bakım betikleri ve satıra özgü yan etkili işlemler tipik örneklerdir.

Cursor nedir ve neden son çare sayılır?

İlişkisel veritabanları kümelerle çalışır: bir UPDATE ifadesi tablonun tüm uygun satırlarını tek hamlede günceller, veritabanı motoru da bu işi bir bütün olarak planlar. Cursor ise bu modeli bilinçli olarak bozar. Sorgunun sonucunu açar, ilk satırı alır, işler, sonra sıradakine geçer. Programlamadaki foreach döngüsüne benzer ve bu yüzden programcıya tanıdık gelir.

Sorun tam da bu tanıdıklıktadır. Küme tabanlı düşünmeyi öğrenmemiş biri her problemi cursor ile çözmeye eğilir. Oysa satır satır işlemde motor kümeyi optimize edemez, her adımda ayrı çalışır ve uzun sürebilir. Bu yüzden iyi bir kural: önce tek ifadeyle çözmeyi deneyin, mümkün değilse cursor'a geçin.

Cursor yine de gerçek bir araçtır. Her satır için ayrı bir saklı yordam çağırmak, her tablo üzerinde sırayla bir bakım komutu çalıştırmak ya da satıra özgü yan etkisi olan bir işlemi tetiklemek gerektiğinde, tek ifade yetmez ve imleç doğru seçimdir.

Cursor mu, küme tabanlı sorgu mu?

Karar verirken işin doğasına bakın. Aynı kural tüm satırlara uygulanıyorsa küme tabanlı ifade yazın. Bir satırın sonucu önceki satırlara bağlıysa (birikimli toplam, önceki değerle fark) çoğu zaman pencere fonksiyonu yeterlidir. Satır başına bir yordam çağırmak ya da dış sistemle konuşmak gerekiyorsa cursor hakkını kazanır.

Örneğin müşteri tablosundaki toplam sipariş tutarı sütununu güncellemek küme işidir: siparişleri müşteriye göre toplayıp tek UPDATE ile yazarsınız. Buna karşılık her aktif müşteri için ayrı bir özet oluşturma yordamını çalıştırmak, yordamın içeriğine bağlı olarak imleç gerektirebilir; yordam kendi içinde tek ifadeye dönüştürülemiyorsa döngü kaçınılmazdır. Sorunun cevabı çoğu zaman 'yordamı kümeye çevirebilir miyim?' sorusunda yatar.

Şekil 1

Satırlar arası bağımlılık ve yan etki, aracı belirler

Cursor ancak tek ifadeyle çözülemeyen işte devreye girer

Elinizdeki iş hangi araca uygun?
  1. Aynı kural tüm satırlara uygulanıyorKüme tabanlı ifadeUPDATE ile JOIN ya da toplulaştırma
  2. Satır, önceki ya da sonraki satıra bağlıPencere fonksiyonuBirikimli toplam, fark, sıralama
  3. Her satır için yordam ya da dış çağrı gerekiyorCursorKapsamı LOCAL tutun, hızlı ve salt okunur seçin
  4. Tablo adları gibi nesneler üzerinde sırayla komut çalışıyorCursor ile bakım betiğiGenellikle dinamik SQL ile birlikte
Kaynak: Sayfa içeriğiMindset Institute

Yaşam döngüsü: DECLARE'dan DEALLOCATE'e

T-SQL belgesi imleç komutlarının görevini açıkça ayırır: DECLARE CURSOR imleci tanımlar, OPEN sonuç kümesini doldurur, FETCH bir satır döndürür, CLOSE mevcut sonuç kümesini serbest bırakır, DEALLOCATE ise imlecin kullandığı kaynakları bırakır. Tipik bir iskelet şöyledir:

DECLARE @musteri_id INT, @ad NVARCHAR(MAX);

DECLARE musteri_imleci CURSOR LOCAL FAST_FORWARD FOR SELECT musteri_id, musteri_adi FROM musteri WHERE aktif = 'E';

OPEN musteri_imleci; FETCH NEXT FROM musteri_imleci INTO @musteri_id, @ad;

WHILE @@FETCH_STATUS = 0 BEGIN EXEC dbo.musteri_ozeti_olustur @musteri_id; FETCH NEXT FROM musteri_imleci INTO @musteri_id, @ad; END;

CLOSE musteri_imleci; DEALLOCATE musteri_imleci;

Önemli nokta, FETCH NEXT'in iki kez yazılmasıdır: biri döngüden önce ilk satırı almak, diğeri döngünün sonunda sıradaki satıra geçmek için. İkincisi unutulursa @@FETCH_STATUS hiç değişmez ve döngü sonsuza kadar ilk satırı işler. Döngü koşulu @@FETCH_STATUS = 0 olduğunda sürer; satır kalmadığında ya da getirme başarısız olduğunda değer değişir ve döngü biter.

Şekil 2

Her tur bir FETCH ile başlar ve durum denetimiyle biter

Döngü, satır kalmayana kadar tekrarlanır

  1. OPENSorgu çalışır ve sonuç kümesi imlece yüklenir.
  2. FETCH NEXTSıradaki satır değişkenlere okunur.
  3. İşleSatır için gereken yordam çağrısı ya da komut çalışır.
  4. Durumu denetle@@FETCH_STATUS = 0 ise yeni tur başlar, değilse döngüden çıkılır.
Kaynak: Kaynak: Microsoft Learn, DECLARE CURSORMindset Institute

Seçenekler: kapsam, yön ve salt okunurluk

DECLARE CURSOR bildirimi, imlecin davranışını belirleyen seçenekler taşır. Günlük işte bilmeniz gerekenler şunlardır.

  • LOCAL: imlecin kapsamı yalnızca tanımlandığı toplu iş, yordam ya da tetikleyiciyle sınırlıdır ve iş bitince imleç otomatik serbest bırakılır. GLOBAL ise bağlantı boyunca görünür kalır ve ancak bağlantı kapanınca serbest kalır. Çakışma ve sızıntıyı önlemek için çoğu durumda LOCAL tercih edilir.
  • FORWARD_ONLY: imleç yalnızca ileri gider. SCROLL ise başa dönme, geri gitme gibi seçenekleri açar ama gerektiğinde maliyet getirir.
  • READ_ONLY: imleç üzerinden güncelleme yapılmasını engeller.
  • FAST_FORWARD: yalnızca ileri giden, salt okunur ve performans iyileştirmeleri açık bir imleçtir. Satırları yalnızca okuyup işleyen döngüler için uygun varsayılandır.
  • STATIC: imleç açıldığındaki veri kopyası üzerinde çalışır; sonradan tablodaki değişiklikler imleçte görünmez.

İmleç üzerinden satır güncelleyecekseniz FOR UPDATE ile tanımlayıp UPDATE komutunda WHERE CURRENT OF kullanırsınız. Çoğu döngüde bunun yerine anahtar sütunla ayrı bir UPDATE yazmak daha anlaşılır olur. Belgeye göre kümelenmiş columnstore indeksi olan bir tabloda imleç ve tetikleyici kullanılamaz; bu kısıt kullandığınız tablo yapısı için geçerliyse imlece hiç başlamayın.

Aynı işi küme tabanlı yazmak

Satır satır yazılmış bir döngüyü küme tabanlı karşılığıyla yan yana koymak, farkı en iyi gösterir. Her müşterinin toplam sipariş tutarını müşteri tablosuna yazan bir işlemi düşünün. Cursor ile müşterileri tek tek gezer, her biri için toplamı hesaplar ve ayrı bir UPDATE çalıştırırsınız. Küme tabanlı karşılığı tek ifadedir:

UPDATE m SET m.toplam_tutar = t.toplam FROM musteri AS m JOIN ( SELECT musteri_id, SUM(tutar) AS toplam FROM siparis GROUP BY musteri_id ) AS t ON t.musteri_id = m.musteri_id;

Küme tabanlı sorgu daha kısadır, motorun kümeyi bir bütün olarak planlamasına izin verir ve yarıda kalırsa tek bir işlemde geri alınır. Birikimli toplam gibi satırlar arası bağımlılık içeren işler de çoğu zaman döngü gerektirmez; pencere fonksiyonları bunları doğrudan ifade eder.

Dönüştürürken şu soruyu sorun: döngünün içinde yaptığım şeyi bir tablo üzerinde ifade edebilir miyim? Cevap evetse, imleç kaldırılır. Cevap hayırsa (örneğin çağırdığınız yordam kendi başına bir iş akışı yürütüyorsa) imleç sağlam bir tercihtir; yeter ki kapsamını dar tutun ve kaynakları serbest bırakın.

Sık hatalar ve önlemler

  • FETCH NEXT'i döngü içinde unutmak: durum değişmez, döngü sonsuz sürer. FETCH'i hem döngüden önce hem döngünün sonunda yazın.
  • CLOSE ve DEALLOCATE'i atlamak: özellikle GLOBAL imleçlerde bağlantı açık kaldıkça kaynak tutulur ve aynı adlı imleç yeniden tanımlanırken hata alınır. İmleci LOCAL tanımlayın ve işi bitince yine de kapatın.
  • Gereksiz yere güncellenebilir imleç açmak: salt okunur ve ileri giden bir imleç (FAST_FORWARD) çoğu döngü için yeterlidir.
  • Uzun süren döngüyü tek işlem içinde tutmak: kilitler döngü boyunca açık kalır ve diğer oturumları bekletir. İşlem sınırlarını bilinçli belirleyin; işlem yönetimi rehberi bu konuyu ayrıca anlatır.
  • Döngü içinde hata yönetimi yazmamak: bir satırın hatası tüm döngüyü durdurabilir. Satır başına TRY...CATCH ile hatayı yakalayıp kaydedin.
  • İsim karışıklığı: arama sonuçlarında 'cursor' bazen Cursor adlı yapay zeka destekli kod editörüne gider. Veritabanı imleci o araçla ilgisi olmayan, T-SQL ve benzeri dillerin bir özelliğidir.

Son olarak sınama: cursor ile yazdığınız bir işi önce küçük bir veri alt kümesinde çalıştırın, sonucu küme tabanlı karşılığıyla ya da elle hesapla karşılaştırın ve ancak sonra tüm veriye uygulayın.

Cursor ve küme tabanlı SQL için Mindset programları

Cursor'ı doğru kullanmak için önce küme tabanlı SQL'i sağlam bilmek gerekir. SQL ile Programlama Eğitimi, veri işleme dili, sorgular, toplama ve gruplama ile görünümler gibi temel konuları ayrı modüllerde işler; bu zemin, hangi işin döngüsüz çözülebileceğini görmenizi sağlar.

SQL ile Veri Sorgulama (İleri) programı ise alt sorgular ve korelasyon teknikleri, görünümler, stored procedure ve tetikleyiciler ile pencere fonksiyonlarını kapsar. Cursor kullanılan bir betiği küme tabanlıya çevirmek isteyen veri analistleri ve raporlama geliştiricileri için uygun bir devamdır. Program kapsamı ayrıntısı eğitim sayfalarında yer alır.

İlgili Mindset programı

SQL ile Programlama Eğitimi

Sık sorulanlar

Sık sorulan sorular

Cursor kullanmak her zaman kötü müdür?

Hayır. Küme tabanlı çözüm mümkünken cursor seçmek kötü alışkanlıktır, çünkü motor kümeyi optimize edemez. Ama satır başına yordam çağırmak ya da nesneler üzerinde sırayla komut çalıştırmak gibi tek ifadeyle ifade edilemeyen işlerde cursor yerinde bir araçtır. Kural basit: önce kümeyi deneyin, olmuyorsa imleci LOCAL ve salt okunur tanımlayarak kullanın.

LOCAL ile GLOBAL arasındaki fark nedir?

LOCAL imlecin kapsamını tanımlandığı toplu iş, yordam ya da tetikleyiciyle sınırlar ve iş bitince imleç otomatik serbest kalır. GLOBAL imleç ise bağlantı boyunca, başka yordamlardan ve toplu işlerden de erişilebilir kalır ve ancak bağlantı kapanınca serbest bırakılır. Bildirimde ikisi de yazılmazsa varsayılan, veritabanının yerel imleç ayarına göre belirlenir.

FAST_FORWARD ne anlama gelir?

FAST_FORWARD, yalnızca ileri giden ve salt okunur bir imleci performans iyileştirmeleri açık biçimde tanımlar. İmleç üzerinden güncelleme yapmayan, satırları baştan sona bir kez okuyup işleyen döngülerin çoğu için uygun seçenektir. SCROLL ya da güncelleme seçenekleriyle birlikte kullanılamaz.

CLOSE ile DEALLOCATE arasındaki fark nedir?

CLOSE, imlecin o anki sonuç kümesini serbest bırakır; imleç tanımı yerinde kalır ve OPEN ile yeniden açılabilir. DEALLOCATE ise imlecin kullandığı kaynakları tamamen bırakır; bundan sonra aynı imleç adı yeniden tanımlanabilir. İş bittiğinde ikisi de sırayla çalıştırılır, LOCAL imleçlerde kapsam bitince ikincisi örtük olarak yapılır.

Cursor yerine ne kullanabilirim?

İşe göre değişir: tüm satırlara aynı kural uygulanıyorsa UPDATE ile JOIN ya da toplulaştırmalı alt sorgu, satırlar arası bağımlılık varsa pencere fonksiyonları, adım adım üretilen sonuçlar için CTE ya da geçici tablo. Bir yordamı satır başına çağırmanız gerekiyorsa önce yordamın kendisini kümeyle çalışacak biçimde yeniden yazıp yazamayacağınıza bakın.

SQL Cursor (İmleç) Kullanımı: Örnek ve Alternatifler