Sql Server Cursor Yazma

Herkese merhaba, Sql Server yazılarımıza kaldığımız yerden devam ediyoruz. Bu yazımızda cursorlardan ve cursor oluşturmadan bahsedeceğim. Hadi başlayalım !
cursor, bir Sql sorgusu sonucu üzerinde dolaşmayı sağlayan yapılar ve birden çok veri dönen sorgularda her bir sorgu sonucu üzerinde işlem yapmak isteniyorsa kullanılır. Örneğin bir tabloda 1 milyon veri var diyelim ve bu tablodaki bir id sütunundaki değerlere göre bir başka tabloda güncelleme yapmak istiyorsunuz ve sisteminiz (web sitesi, mobil uygulama, masaüstü program vs) çok yoğun bir şekilde kullanılıyor. Böylesi bir durumda 1 milyon veriyi çekip tek seferde başka bir tablodan güncelleme işlemi yapmak sunucuyu bir hayli yoracağından tek seferde güncelleme yapmak yerine cursor kullanarak teker teker güncelleme yapmak daha faydalı olacaktır. Tabi her veri tek tek gezilip işlem yapılacağından tek seferde yapılmasına göre daha uzun sürer fakat daha güvenli ve sunucu açısından performanslı olur. Cursor arka tarafta işlemini yaparken sunucu kendisine gelecek olan farklı isteklere rahatlıkla cevap verebilir. Ayrıca oluşabilecek bir hatada transaction rollback yapma ve transaction kontrolleri daha hızlı bir şekilde yapılabilmektedir.
cursor, değişken gibi davranırlar ve belirttiğiniz bir Sql sorgusu içerisinde çalışırlar. İşlem tamamlandıktan sonra yani cursor ile işiniz kalmayınca kapatılıp bellekten silinmeleri gerekmektedir. Bellekten silinmedikleri takdirde açık kalacaklar ve bellekte yer kaplayacaklardır. Ayrıca kapatılmamaları halinde ise kullandıkları Sql sorgusundaki tablolara erişim bazen mümkün olmamaktadır. Değişken gibi davrandıkları için değişken gibi oluşturulurlar, tam yapıları ise şöyledir;
declare cursor_adı cursor for sql_sorgusu
Buradaki cursor_adı kısmına cursor’a vermek istediğiniz adı, sql_sorgusu kısmına ise cursor’un çalışacağı Sql sorgusunu yazmanız gerekiyor. Fakat dikkat etmeniz gereken şey; cursorlar sadece select sorgularıyla çalışabilirler, update, delete ve insert sorgularına çalışamazlar. Çünkü size geri dönüş olarak veri veren tek sorgu tipi select. Yani update, delete ve insert sorgularında size sadece (x row affected) şeklinde bir dönüş yapılır fakat select sorgularında doğrudan sorgu yaptığınız tablo(lar) içerisindeki verileri getirir. cursorlar da Sql sorgusu sonucunda dönen veriler üzerinde gezebildiği için sadece select sorgularında çalışmaktadırlar. Bu şekilde cursor oluşturulabiliyor fakat cursor oluşturmak demek onu kullanabileceğimiz anlamına gelmiyor. Şu an sadece bellekte bir tane cursor oluşturduk ve öyle bomboş duruyor.
cursorları kullanabilmek için onları açmamız gerekiyor. Açılmadığı sürece kendilerine verilmiş olan Sql sorgusunu çalıştırmayacak ve veriler üzerinde işlem yapılamayacaktır. cursor’u açmak için
open cursor_adı
cursor açıldı ve ilk veriyi okumak için hazır bekliyor. cursor üzerinden veri okumak için ise şöyle bir kod yazılması gerekiyor;
fetch next from cursor_adı
Bu kod ile tüm sonuçlar içerisinde cursor’un bulunduğu noktanın bir sonrasındaki veri okunur ve ele alınır. Yani her bir fetch next from cursor_adı yazımında cursor bir sonraki veriye geçer ve onun üzerinde işlem yapar. Fakat cursor’un okuma yaptığı verilerin sayısını bilmiyorsak yada çok fazla veri varsa ve her bir veride aynı işlem yapılacaksa sürekli fetch next from cursor_adı şeklinde yazıp sonrasında yapılacak işlemleri yazmayacağız elbette. Yani örneğin 1000 tane veri geldi ve siz bu 1000 veri üzerinde işlemler yapacaksanız cursor’u 1000 kere elle ilerletmeyecek, while döngüsü gibi bir nimeti kullanacaksınız. Peki ilerletme olayı nasıl olacak diye soracaksanız da şöyle anlatayım;
cursor ile bir Sql sorgusu gönderince ve sorgu sonucunda veri geliyorsa @@fetch_status adında bir tane sistem değişkeni değeri sistem tarafından 0 yapılıyor, boş veri gelirse yani veri gelmezse de @@fetch_status değişkeni değeri -1 oluyor. Ayrıca Sql sorgusu sonucunu cursor ile gezdiğinizde ve ilgili cursor adımında veri varsa @@fetch_status değişkeni değeri 0, veri yoksa -1 değerini alıyor. Dolayısıyla @@fetch_status değişkeni değerini kullanarak while döngüsüne sokabilir ve her bir while döngüsü adımında cursor’u bir adım öteye götürebiliriz. Yani while döngüsü içerisinde @@fetch_status değişkeninin değerini kontrol edeceğiz demektir.
cursor’u açtıktan sonra ilk veriye geçmesini söylüyoruz ve sonrasında while döngüsüne girerek @@fetch_status değeri 0 olmadığı sürece döngü bloğu içerisinde yapacağımız işlemleri yapıyor ve sonra cursor’u bir sonraki veriye geçiriyoruz. Yani şöyle bir yapımız olacak;
open cursor_adı
fetch next from cursor_adı
while @@fetch_status = 0
begin
-- while içerisinde yapılacak işlemler
fetch next from cursor_adı
end
Her while adımında cursor bir sonraki veriye geçeceği için ve döngünün başına dönüldüğünde de @@fetch_status değişkeninin değerine bakılacağı için ve ilgili cursor adımında veri varsa yani @@fetch_status değeri 0 ise döngüye devam edilecek, veri bittiğinde @@fetch_status değeri -1 olacağından döngüden çıkılacaktır. Yani cursor ile gönderdiğimiz Sql sorgusundan gelen veriler en baştan en sona kadar gezilecek ve her bir adımda istediğimiz işlemleri yapmış olacağız. Örneğin kitaplar adında bir tane tablonun olduğunu varsayalım ve bu tablodan cursor yardımıyla tüm verileri çekip her bir veri için ekrana ‘SoftwareSup.Net’ yazdıralım.
declare kitapCursor cursor for select * from kitaplar
fetch next from kitapCursor
while @@fetch_status = 0
begin
select 'SoftwareSup.Net'
fetch next from kitapCursor
end
cursor ile veri üzerindeki sütunların değerini de alabiliriz. Bunun için cursor’a gönderdiğimiz Sql kodu ile istediğimiz sütunları ayrı ayrı vermemiz ve cursor’u ilerletirken bu sütunları bir değişkene atayabiliyoruz. Tabi atayacağımız değişkeni cursor’dan önce oluşturmamız gerekiyor.
Örneğin kitaplar tablosunda birde varchar tipte kitapAdi sütunu olduğunu varsayalım ve cursor yardımıyla tüm kitapların kitapAdi değerini Sefiller olarak güncelleyelim. Güncelleme yapmak için de kitaplar tablosundaki id sütununu kullanalım. Yani şöyle bir Sql kodu yazmamız gerekiyor;
declare @id int
declare kitapCursor cursor for select id from kitaplar
fetch next from kitapCursor into @id
while @@fetch_status = 0
begin
update kitaplar set kitapAdi = 'Sefiller' where id = @id
fetch next from kitapCursor into @id
end
Burada @id değişkenini oluşturduk ve cursor’a into anahtar kelimesi yardımıyla ekledik. Ardından while döngüsü içerisinde cursor’u ilerletirken de @id değişkenini into anahtar kelimesi yardımıyla cursor içerisine ekledik. Bu sayede her bir veride id sütununun o anki değeri @id değişkenine atanacak ve bu değişkeni kullanabiliyor olacağız. where kriterinde değişkenleri de kullanabildiğimiz için @id değişkenini doğrudan ekledik. Burada yaptığımız işlemi cursor ve while döngüsü kullanmadan doğrudan update kitaplar set kitapAdi = ‘Sefiller’ komutu ile de yapabilirdik fakat örneğin 1 milyon gibi çok fazla verinin olması durumunda bu sorgu sunucuya büyük bir yük bindirecek ve sunucuyu yavaşlatacaktır. Sunucu yavaşladığı için de kendisine gelen istekleri daha geç cevap vermeye başlayacaktır. Özellikle hız gerektiren işlemlerde bu tam anlamıyla bir dezavantaj ve müşteri kaybı demektir. Örneğin bankacılık gibi çok ciddi verilerin bulunduğu ve hızın çok önemli olduğu bir sektörde cursor kullanmadan tüm verileri güncellemeye kalktığınızda o sunucuyu kullanan tüm işlemler etkilenecektir. Örneğin müşteri kayıtlarının bulunduğu tabloda bu şekilde bir kullanım yapmak istediğinizde POS, online, kredi kartı, banka kartı gibi doğrudan müşteriyle ilgisi bulunan işlemler ciddi anlamda yavaşlayacak ve bu da müşteri şikayetlerine yol açacaktır. cursor kullanmak sorgu süresini gerçek manada arttıracaktır fakat diğer işlemlerin etkilenmemesi için en mantıklı yollardan biri olacaktır.
cursor’u oluşturup açtık ve işlemlerimizi yaptık fakat cursor hâlâ açık bekliyor, bu bellek kaybı demek olacağından cursor’u kapatmamız ve sonrasında bellekten silmemiz gerekiyor. Bellekten silmezsek bellekte kayıtlı kalacak ve aynı kodu bir daha çalıştırmak istediğimizde “böyle bir cursor zaten var” diye hata verecektir. Dolayısıyla bellekten silmek hem bellek kaybını önleyecek hemde aynı kodu tekrardan çalıştırmak veya aynı isimde bir cursor daha oluşturmak istediğimizde hata vermesinin önüne geçecektir. Açık olan bir cursor’u kapatmak ve bellekten silmek için şöyle bir yapı kullanılmakta;
close cursor_adı
deallocate cursor_adı
close cursor_adı, açık olan cursor’u kapatacak ve deallocate cursor_adı ise bellekten kalıcı olarak silecektir.
Evet Sql Server’da cursor bu şekilde kullanılıyor. Tüm Sql Server yazılarımıza buraya tıklayarak ulaşabilirsiniz. Herkese hayırlı günler.