İmleç (Cursor) Kullanımı

Eyl 08, 2013

Bilgisayar ile uğraşırken sürekli olarak bir yerlere bilgi girilmesi gerekir. Bilgi girişinde en çok kullanılan yardımcı imleç (cursor) 'tir. Bilgisayarın o an hangi alanla ilgilendiğini gösteren imleç aynı mantıkla SQL Server üzerinde de tanımlanmıştır.

İmleçler, SQL Server üzerinden döndürülen belli bir kayıt kümesi üzerinde satır satır işlem yapabilmeye olanak sağlayan veritabanı nesneleridir.

Satır bazında işlem yapmak aslında programlama ile uğraşanlar için sıradan işlerdendir. Bu tarz bir işlem için genel olarak aşağıdaki adımlar sırayla izlenir;

  1. Veritabanından veri yığını istemciye çekilir.
  2. Veri üzerinde, yüksek seviyeli programlama dilleri kullanılarak yazılmış kodlar sayesinde istenilen değişiklikler yapılır. Yüksek seviyeli dillerde sunulan döngü yapıları sayesinde her kaydı tek tek incelemek işten bile değildir.
  3. Elde edilen sonuç kümesi veritabanına gönderilir.

Burada üzerinde durulacak olan ikinci yöntem ise aynı işlemleri veritabanı sunucusu üzerinde yapmaktır.

İki farklı yöntemin de en uygun olduğu durumlar vardır. Örneğin: Satır bazında Transact-SQL ile yapılamayacak karışık işlemler istemci tarafında yüksek seviyeli bir dil ile yazılacak kodlar ile kolaylıkla yapılabilir. Ya da Transact-SQL ile yapılabilecek satır bazında işlemleri, sunucuda imleçleri kullanarak halledebilir ve bu sayede ağ trafiğinin azalması sağlanabilir.

Yüksek seviyeli dillerde sağlanan döngü yapıları yerine sunucuda imleçleri kullanılır. İmleçlerin kullanım yerlerini örnekler üzerinde incelenecektir. Örnekler için SQL Server 2000 ile gelen pubs örnek veritabanı kullanılacak. İlk örnek titles tablosunda bulunan kitapların fiyatlarını güncellemek olacaktır.

İş planına göre;

•  20$ altındaki fiyatlar için artış oranının %10 
•  20$ ve üzerindeki fiyatlar için artış oranının %5

olduğu kabul edilsin. Bunun için aşağıdaki standart Transact-SQL ifadeleri kullanılabilir;

UPDATE titles 
SET price = price * (price * .1) 
WHERE price < 20 
UPDATE titles 
SET price = price * (price * .05) 
WHERE price >= 20

Burada şöyle bir sorun oluşabilir. Kitap fiyatı 19.95$ ise ilk ifadede %10'luk artış uygulanacak, bu sayede 20$'ın üstüne çıktığı için ek olarak %5'lik bir artış daha uygulanacak ve istenmeyen sonuçlar oluşacaktır.

Bu sorunu aşmak için satırları tek tek ele alıp onlar üzerinde işlem yapmak üzere imleçler kullanılacaktır.

Temel bir imleç ifadesi aşağıdaki gibi yazılır.

USE pubs 
GO 
DECLARE cr_fiyatlar CURSOR FOR 
SELECT price FROM titles 
OPEN cr_fiyatlar 
FETCH NEXT FROM cr_fiyatlar 
WHILE @@FETCH_STATUS = 0 
FETCH NEXT FROM cr_fiyatlar 
CLOSE cr_fiyatlar 
DEALLOCATE cr_fiyatlar

Bu ifade Sorgu Çözümleyici'de çalıştırıldığında şekilde görünene benzer bir sonuç listesi elde edilir.



İfadede kullanılan terimlere geçmeden önce, imleç kullanmadan sadece "SELECT" ifadedesi ile aynı veriler elde edilmeye çalışılsaydı nasıl bir sonuç ortaya çıkacaktı ona bakılmalıdır. Aşağıdaki şekilde, imleçleri kullanmadan, veriler aynı "SELECT" ifadesiyle ( "SELECT price FROM titles" ) seçildiğinde alınan sonuç görülebilir.



İmleç kullanıldığında birer satır içeren sonuç kümeleri döner; fakat sadece "SELECT" ifadesi kullanıldığında tüm sonuçlar tek bir küme olarak döner.

Şimdi imleç ifadesinde kullanılan terimler incelenebilir:

DECLARE cr_fiyatlar CURSOR FOR 
SELECT price FROM titles

Bu ifade yeni bir imleç tanımlamak için kullanılır. "declare" ifadesinden sonra imlecin adı belirtilir. Addan hemen sonra gelen "cursor for" ifadesinden sonra ise imlecin hangi veriler için tanımlanacağı belirtilir.

Örnekte ismi cr_fiyatlar olan bir imleç  SELECT price FROM titles  ifadesi ile dönecek olan veri kümesi içinde çalışmak üzere tanımlanıyor. Bu ifade ile dönecek olan veri kümesi üzerinde, tanımlanan imleci kullanarak satır satır gezme imkânı olacaktır. İmlecin tanımlanacağı veri kümesini tanımlayan "SELECT" ifadesi içinde, "where", "group by" ve "order by" gibi yardımcı Transact-SQL komutları da kullanılabilir. 

OPEN Deyimi:

OPEN 
OPEN cr_fiyatlar

"Open" deyimi, belirtilen imleç için bellekte yer ayırır ve ilk kullanım için gerekli ayarları yapar. Artık imleç ilk kullanım için hazırdır. 

FETCH 
FETCH NEXT FROM cr_fiyatlar

"FETCH NEXT" deyimi tek bir kaydı alır ve imlece yükler. "NEXT" ifadesi yerine PRIOR (önceki), FIRST (ilki) ve LAST (sonuncusu) gibi ifadeler de kullanılabilir. 

@@FETCH_STATUS 
WHILE @@FETCH_STATUS = 0 FETCH NEXT FROM cr_fiyatlar

Örnekte imleç "while" döngüsü ile kontrol edilmiştir. @@FETCH_STATUS ifadesi döngünün kontrol birimidir. "Fetch" ifadesinin sonucuna göre üç farklı değer alabilir. Bunlar ;

0: Bir önceki "fetch" ifadesi başarılı 

-1: Bir önceki "fetch" ifadesi hata ile karşılaştı 

-2: Son kayıt. 

Örnekte WHILE @@FETCH_STATUS = 0 ifadesi ile hata olmadığı sürece bir sonraki kayıt elde edilir.

Bir imlecin içine farklı bir imleç de yerleştirilebilir. Alternatif olarak bir imleç, içinde imleç bulunan bir saklı yordam çağırabilir. Böyle bir durumda @@FETCH_STATUS değerine dikkat etmek gerekir. Çünkü bu fonksiyon döngü düzeyinde değil bağlantı düzeyinde kontrol yapar.

CLOSE

CLOSE cr_fiyatlar

Kayıtlar imlece yüklendiğinde cursor lock (imleç kilidi) oluşur. Bu kilit, CLOSE ifadesine kadar serbest bırakılmaz. "Close" ifadesinden sonra imleç tekrar açılabilir. Kapatıp açınca imleç hiçbir özelliğini kaybetmiş olmaz. Yani bellekten atılmış değildir. "Close" ile kapatmaktaki amaç kilidi açmaktır.

DEALLOCATE

DEALLOCATE cr_fiyatlar

"Close" ile imleç bellekten temizlenmez. İmlece ihtiyaç kalmadığında kaldırmak için kullanılan bellek alanı boşaltılmalı ve değişkenler yok edilmelidir. "Deallocate" bu işlemleri tek seferde yapar. Bu işlemden sonra imleç bellekten kaldırılmıştır.

İmleçler sayesinde veri kümeleri üzerinde değil, daha çok kullanılan yapısal sorgulamalara benzer şekilde satır satır işlem yapılabilir. Bu bir miktar performans kaybını beraberinde getirir; ama bazı durumlarda bu, yapılacak karmaşık işlemlerin basitleşmesinden dolayı kabul edilebilir. Buraya kadar bir imlecin genel olarak nasıl yazıldığı görüldü. Şimdi yazının başında kurulan senaryonun uygulanmasına geçilsin.

WHERE CURRENT OF

Yukarıdaki örnekte imlecin nasıl tanımlandığını görmek için sadece kayıtlar listelendi; fakat çoğu zaman imleçleri sadece verileri listemek için kullanmak performansı olumsuz etkileyecektir. İmleçler gerçekten ihtiyaç olduğunda kullanılmalıdır. Örnekte güncelle yapmak gerektiğinden, örnek ifade güncelleme yapılmak üzere aşağıdaki gibi yenilenmelidir.

  DECLARE @fiyat MONEY 
  DECLARE @ cr_fiyatlar CURSOR 
  SET @ cr_fiyatlar = CURSOR FOR 
        SELECT price FROM titles

  OPEN @ cr_fiyatlar 
  FETCH NEXT FROM @ cr_fiyatlar INTO @fiyat

  SELECT price FROM titles

  WHILE ( @@FETCH_STATUS = 0) 
  BEGIN 
        IF @fiyat < 20 
              UPDATE titles SET price = (@fiyat + (@fiyat * .1)) 
              WHERE CURRENT OF @ cr_fiyatlar 
        ELSE 
              UPDATE titles SET price = (@fiyat + (@fiyat * .05)) 
              WHERE CURRENT OF @ cr_fiyatlar 
  FETCH NEXT FROM @ cr_fiyatlar INTO @fiyat 
  END

  SELECT price FROM titles

  CLOSE @ cr_fiyatlar

  DEALLOCATE @ cr_fiyatlar 

Yukarıda görüldüğü gibi yazılan Transact-SQL ifadesi sayesinde istenilen sonuç elde edildi. 20$ altındaki fiyatlar %10, 20$ ve üzerindeki fiyatlar %5 arttırılmış durumda. Bu Transact-SQL ifadesinde kullanılan "SELECT" ifadeleri;

"SELECT price FROM titles"

Sadece sonucu görmek içindir. Bu ifade olmasaydı güncelleme işlemi yine yapılacak; fakat sonuçlar listelenmeyecekti.

Burada asıl dikkat edilmesi gereken "WHERE CURRENT OF" ifadesi. Bu ifade SQL Server'a o an imlecin gösterdiği kayıt ile çalışmasını söyler.

İmleçlerin performans açısından çok tercih edilmediği belirtilmişti. Bu yüzden gerekmedikçe imleç kullanılmamalıdır. Yukardaki örnek için, Transact-SQL ifadesini biraz genişleterek (örneğin "case" yapısı ya da geçici tablolar kullanarak) imleç kullanmadan aynı sonuç elde edilebilirdi.