BilgiTeknoloji.net    
b i l g i   t e k n o l o j i   y a z ı l ı m

Ana Sayfa

Marjinal XML Access Pratik Uygulamalar Projeler Ekonometri Dilimiz Editörden Çetrefil İletişim
 

SQL Server ve Kullanıcı Tanımlı İşlevler

Veritabanlarında sorgu yapmak için temel SQL dilinde yardımcı bazı işlevler mevcut. Bunların yeterli olmadığı durumlarda veritabanı sistemlerinde kullanıcı tanımlı fonksiyonlar (user defined functions) oluşturulabiliyor.

SQL Server’da kendi işlevlerinizi yazıp bunları tüm sorgularda veri dönüşümleri için kullanabilirsiniz. Ama SQL Server’daki işlevler değer üretmekle kalmıyor, FROM öbeklerinde kullanılabilecek şekilde kayıt kümesi üretebiliyorlar.

  • SQL Kodlarında Özel İşlevler
  • Kullanıcı Tanımlı İşlevler (User-Defined Functions)
  • İşlevden Kayıt Kümesi Üretmek


SQL KODLARINDA ÖZEL İŞLEVLER


SQL dilinde geliştiriciler için bazı temel fonksiyonlar mevcuttur. Veriler üzerinde dört işlem ve karakter dönüşümleri dışında mutlak değer, üs ve karekök almak, matematiksel hesaplar yapmak için bazı komutlar oluşturulmuş. Bununla birlikte standart SQL dili çok gelişmiş fonksiyonlar içermez. Bu yüzden veritabanı sistemlerinde bu dil genişletilip yeni diller oluşturulmuş.

SQL içinde fonksiyon kullanımının en kolay yapıldığı yerlerden biri Access’tir. Access modülleri içindeki global tüm fonksiyonlar veritabanı genelindeki her SQL kodu içinde kullanılabiliyor. Fakat bu sadece Access veritabanı içinde çalışırken geçerli oluyor.

SQL Server, Interbase gibi veritabanı sunucuları ise hem kendi fonksiyonlarının ve kayıtlı yordamlarının hem de özel hazırlanmış DLL yordamlarının SQL kodlarında kullanılmasına izin veriyorlar.

SQL kodlarında özel fonksiyon kullanma ihtiyacı hangi durumlarda oluşabilir? Standart SQL kodlamasında yer almayan işlevler, karakter dönüşümleri, sayısal fonksiyonlar, karmaşık fonksiyonlar oluşturmaya ihtiyaç duyulabilir. Her fonksiyon veri alanlarını alıp işleyebilir ya da tek başlarına yeni değerler ya da kayıt kümeleri üretebilirler.

Bu ayki uygulamamızda Access ve SQL Server’da oluşturduğumuz fonksiyonlarımızı kişilerin doğum günü bilgilerini listelemek için kullanıyoruz.

Uygulamamızda kişiler tablosunda bulunan doğum tarihi bilgisini tek başına kullanarak;
- sonraki doğum günü kutlamasının ne zaman olması gerektiği,
- tarihin hangi güne geldiği,
- kişinin kaç yaşına basacağı,
- kutlamaya kaç gün kaldığı
gibi bilgileri öğrenebiliyoruz.

Başlamak için Access’te yeni bir modül (module) oluşturun ya da var olan bir modülü açıp en alta aşağıdaki kodları ekleyin.

Function GunAdi(tarih As Date) As String
  Select Case Weekday(tarih, vbMonday)
    Case 1: GunAdi = "Pazartesi"
    Case 2: GunAdi = "Salı"
    Case 3: GunAdi = "Çarşamba"
    Case 4: GunAdi = "Perşembe"
    Case 5: GunAdi = "Cuma"
    Case 6: GunAdi = "Cumartesi"
    Case 7: GunAdi = "Pazar"
  End Select
End Function

Function SonrakiDogumGunu(DogumTarihi As Date) As Date
  Dim ay As Byte, gun As Byte
  ay = Month(DogumTarihi)
  gun = Day(DogumTarihi)
  SonrakiDogumGunu = DateSerial(Year(Date), ay, gun)
  '''Bu yılkini buldu.
  If SonrakiDogumGunu < Date Then
    '''Bu yıl geçtiyse gelecek yıla sarktı.
    SonrakiDogumGunu = DateSerial(Year(Date) + 1, ay, gun)
  End If
End Function

Modülün içine eklenen yukarıdaki GunAdi ve SonrakiDogumGunu fonksiyonları global tanımlı olup hem diğer tüm modüllerde, hem de Access içinde iken tüm sorgularda (ve tüm SQL kodlarında) kullanılabilirler.

SELECT
    Kisi,
    DogumTarihi,
    SonrakiDogumGunu(DogumTarihi) as KutlamaTarihi,
    GunAdi(KutlamaTarihi) as HangiGun
FROM
    Kisiler



Yukarıdaki fonksiyonların karşılığını doğrudan SQL kodu içinde tanımlamak da mümkündür. Office 2000 ile gelen yeni VBA fonksiyonları bu tür işlemleri kolaylaştırıyor.

CHOOSE fonksiyonu SELECT-CASE bloklarının karşılığıdır. Gün adını Choose ile aşağıdaki şekilde bulabiliyoruz:

Choose(Weekday([KutlamaTarihi]);"Pazar";"Pazartesi";"Salı";"Çarşamba";"Perşembe";"Cuma";"Cumartesi")

Birinci parametrenin değeri, sonucun, devamındaki kaçıncı parametreden alınacağını belirliyor. Sonraki doğum günü tarihini doğrudan bulmak ise biraz daha karmaşık:

DateSerial( IIf(DateSerial(Year(Date()); Month([DogumTarihi]); Day([DogumTarihi]))>=Date(); Year(Date()); Year(Date())+1); Month([DogumTarihi]); Day([DogumTarihi]))

VBA’daki IIF fonksiyonu Sinan’ın doğum gününün bugün olduğunu, DateSerial fonksiyonu da 29 Şubat’ta doğan Sevil için 1 Mart’ta doğum günü partisi yapılması gerektiğini hatırlatıyor.

Örnek dosya: dogumgunleri_mdb.zip


USER-DEFINED FUNCTIONS (KULLANICI TANIMLI İŞLEVLER)

Access dışında SQL Server, Interbase gibi veritabanı sunucularında da bu tür fonksiyonlar kullanılabiliyor. Access’te bizim yazdığımız fonksiyonların buradaki adı User-Defined Functions.

SQL Server’daki fonksiyonlar, değer döndüren standart bir fonksiyondan daha fazlasını yapabilir, bir değer yerine kayıt kümesi döndürebilirler.

Exterprise Manager’da veritabanı altındaki User-Defined Functions ağacında kullanıcı tanımlı fonksiyonlar yer alıyor.

Bu fonksiyonlar parametre alabilmelerine karşın bazı kısıtlamalar sözkonusudur. Parametre değerleri, kayıtlı yordamlarda olduğu gibi dinamik tanımlanamıyor. Bu, fonksiyona parametre olarak gönderilen tanımlı bir değişkenin fonksiyon içerisinde değiştirilemeyeceği anlamına geliyor. Bu durumda sadece fonksiyonun kendi değeri elde edilir. Ayrıca kullanım esnasında bütün parametrelerin belirtilmesi gereklidir. VBA fonksiyonlarında kullanılan Optional gibi bir etiket burada yok ne yazık ki.

Kullanıcı tanımlı fonksiyonları kayıtlı yordamlarla (stored procedure) kıyasladığımızda her ikisinin de diğerine göre avantajları olduğunu görüyoruz. Stored procedure’lerin parametrelere değer atabilmelerine ve kümesi üretebilmelerine karşın fonksiyonlar hem değer döndürürler, hem SQL kodlarından kayıt kümesi üretirler, hem de içeride yeni bir kayıt kümesini geçici olarak oluşturup istemciye gönderebilirler. Bununla birlikte kayıtlı yordamlar (stored procedure'ler), fonksiyonların aksine bir değişkeni içeride değiştirip geri gönderebilirler.

SQL Server’da yeni bir fonksiyon oluşturmak için Enterprise Manager ekranında iken User-Defined Functions ağacı üzerinde sağ tıklatıp New User-Defined Function komutunu seçin.

CREATE FUNCTION fn_gunadi (@tarih DATETIME) RETURNS VarChar(10)
AS  
BEGIN 
  RETURN (  
    CASE (SELECT DATEPART(dw, @tarih))
      WHEN 1 THEN 'Pazar'
      WHEN 2 THEN 'Pazartesi'
      WHEN 3 THEN 'Salı'
      WHEN 4 THEN 'Çarşamba'
      WHEN 5 THEN 'Perşembe'
      WHEN 6 THEN 'Cuma'
      WHEN 7 THEN 'Cumartesi'
    END
   ) 
END



Yukarıdaki fonksiyonun tanımlamasının VBA’daki karşılığı aşağıdakine benzerdir.

Function fn_gunadi(tarih as DateTime) as Varchar ‘yani string

Kullanım aşamasında anlaşılır olması için fonksiyon adları bir gelenek olarak “fn_” ile başlatılıyor. Fonksiyonun değerini atamak için RETURN deyimi kullanılıyor. RETURN'dan sonraki parantez içinde bulunan değer fonksiyonun sonucudur.

Fonksiyonlar oluşturulurken isminin devamında parantez içinde varsa parametreleri virgül ile ayırarak tanımlamak gerekir. Sonrasında RETURNS ile fonksiyonun ne tür değer üreteceği belirtilir.

Fonksiyonları kullanırken sahip adını belirtmek gereklidir. Sahip adı geçerli olarak dbo olabilir. Tarih bilgisinden Türkçe gün adını bulan fonksiyonumuzu aşağıdaki şekillerde kullanmamız mümkün.

SELECT dbo.fn_gunadi('2002-05-01')
SELECT dbo.fn_gunadi(GetDate())
SELECT DogumTarihi, dbo.fn_gunadi(DogumTarihi) FROM Kisiler

Sonraki doğum günü bilgisini bulmak için Access'te olduğu gibi bir fonksiyon daha oluşturmak gerekiyor.

CREATE FUNCTION fn_sonraki_dogum_gunu(@dogumtarihi SMALLDATETIME, @orjin SMALLDATETIME)
  RETURNS SMALLDATETIME
AS  
BEGIN 

  DECLARE @tarih SMALLDATETIME

  -- dogum gününü simdiki yila getir.
  SET @tarih= DATEADD (yy, YEAR(@orjin) - YEAR(@dogumtarihi), @dogumtarihi)

  -- dogum günü geçmisse sonraki yila sarksin.
  IF DATEDIFF(d,@tarih,@orjin) > 0 SET @tarih= DATEADD (yy,1,@tarih)

  RETURN (@tarih)

END

Fonksiyonlar içinde, başka fonksiyonlar kullanılabilse de istisnalar mevcuttur. Örneğin en basit GETDATE() komutu fonksiyonlar içinden kullanılamaz. Bu yüzden yukarıdaki fn_sonraki_dogum_gunu fonksiyonu, Access'tekinin aksine güncel tarih bilgisini doğrudan bulamadığı için @orjin parametresini oluşturmak zorunda kaldık.

Doğum tarihi @orjin değeri ile kıyaslanacak ve @orjin tarihinden sonraki ilk doğum günü bilgisi elde edilmeye çalışılacak.

Sonuç olarak fn_gun_adi ve fn_sonraki_dogum_gunu fonksiyonları nihai doğum günü sorgumuzu oluşturmak için yeterlidir.

SELECT *, YEAR(KutlamaTarihi) - YEAR(DogumTarihi) AS Yas, 
  dbo.fn_gunadi(KutlamaTarihi) AS GunAdi,
 (CASE WHEN DATEDIFF(d,GetDate(),KutlamaTarihi)=0 THEN 'Bugün' ELSE 
       CONVERT(Varchar(15), DATEDIFF(d,GetDate(),KutlamaTarihi) ) 
              + ' gün sonra'
  END) AS NeZaman
FROM
 (SELECT Kisi, DogumTarihi, 
    dbo.fn_sonraki_dogum_gunu(DogumTarihi, getdate()) AS KutlamaTarihi
    FROM Kisiler
  ) AS Tablo
ORDER BY KutlamaTarihi



29 Şubat'ta doğan Sevil için Access'tekine karşılık SQL Server'daki listede 1 Mart yerine 28 Şubat'ta kutlama yapılması gerektiği gösteriliyor.

Access'teki DateSerial komutuna karşılık burada kullandığımız DateAdd fonksiyonu, sonraki ayın ilk günü yerine, o ayın sonunu getirmeyi yeğledi.

AS deyimi sorgu bloklarında seçilen veri alanlarını ya da kayıt kümelerini yeniden isimlendirmeye yarıyor.

  SELECT Kisi as AdiSoyadi, ... FROM Kisiler

şeklindeki bir sorgunun sonucunda Kisi alanı AdiSoyadi adını alır. Yerine eşittir işareti kullanmak da aynı işi yapar.

  SELECT AdiSoyadi=Kisi, ... FROM Kisiler

Bununla birlikte AS deyimi SQL dilinde bir bloğu anlandırmak için de kullanılabilir. Sorgular dışında fonksiyon ve kayıtlı yordamlarda AS deyimi kodun tamamını tanımlamaya ve adlandırmaya yarıyor.


İŞLEVDEN KAYIT KÜMESİ ÜRETMEK

SQL Server'daki kullanıcı tanımlı fonksiyonların bir başka özelliği, değer dışında kayıt kümesi döndürebilmeleri. Bu, fonksiyonun FROM içtümcesi içinde doğrudan kullanılabileceği anlamına geliyor. Bu durumda haftanın günlerini listelemek için bir tablo oluşturmak yerine fonksiyon kullanmak mümkündür.

SQL Server komut dilinde tablolar değişken olarak ifade edilebiliyorlar. Bir fonksiyon içinde yeni bir tablo değişkeni tanımlayıp buna kayıt eklemek ve fonksiyonun sonucu olarak bu tabloyu göndermek kolay.

CREATE FUNCTION fn_gunler( @Tur INT)
/*
  Tur= 0,(BOŞ) Tüm günler
  Tur= 1 Sadece haftaiçi
  Tur= 2 Sadece haftasonu
*/
RETURNS @Gunler TABLE (Sira INT, GunAdi VARCHAR(10) )
AS
BEGIN

IF (@TUR=0 OR @TUR=1) 
BEGIN
  INSERT @Gunler VALUES(1,'Pazartesi')
  INSERT @Gunler VALUES(2,'Salı')
  INSERT @Gunler VALUES(3,'Çarşamba')
  INSERT @Gunler VALUES(4,'Perşembe')
  INSERT @Gunler VALUES(5,'Cuma')
END
IF (@TUR=0 OR @TUR=2)
BEGIN
  INSERT @Gunler VALUES(6,'Cumartesi')
  INSERT @Gunler VALUES(7,'Pazar')
END

RETURN
 
END

fn_gunler @tur parametresinin değerine göre tüm günleri, haftaiçi günleri ya da haftasonu günleri isteleyebiliyor.

SELECT * FROM fn_gunler(1)

cümlesi haftaiçi günleri gösteriyor. Üstteki RETURNS satırında tanımlanan @Gunler tablo değişkeni fonksiyonun sonucu olacak. TABLE deyiminin yanında parantez içinde tablonun alanları tanımlanıyor. Bu tanımlama sadece fonksiyonun o an çalışması için geçerli olup veritabanında bir değişiklik yapılmıyor.

Fonksiyonları denemek için Query Analyzer programı kullanışlıdır. Sorgu ekranında SELECT ve PRINT ifadelerini kullanarak fonksiyonları gözleyebilirsiniz.

  SELECT dbo.fn_gunadi('2002-05-01')

ifadesi sorgu penceresinin altındaki Grids bölmesinde bir tablo gösterirken

  PRINT dbo.fn_gunadi('2002-05-01')

ifadesi sonucu Messages bölmesinde gösterir.


Tablo üretmeyen fonksiyonlarda RETURNS ile birlikte sadece tip tanımlaması yapılırken, tablo değerli fonksiyonlarda sonuç tablosu RETURNS yanında tanımlanıyor. Bu durumda, fonksiyonun sonucunun gönderilmesi için RETURN deyimini tek başına kullanmak yeterli oluyor.

Tablo değerli fonksiyonların satırlarını oluşturmak için INSERT deyimini birkaç şekilde kullanmak mümkün. Kayıtları VALUES ile doğrudan tanımlayabileceğimiz gibi, başka bir tablo değişkeninden ya da bir kayıt kümesinden alıp oluşturabiliriz.

CREATE FUNCTION fn_kisiler()
RETURNS @tablo TABLE (kisi VARCHAR(50) ) 
AS  
BEGIN
  INSERT @tablo values ('KİŞİLER')
  INSERT INTO @tablo
      SELECT kisi FROM kisiler ORDER BY kisi
  RETURN
END

Yukarıdaki fn_kisiler fonksiyonunu FROM içtümcelerinde aşağıdaki gibi kullanabiliriz artık.

SELECT * FROM dbo.fn_kisiler()





 

Dosya: dogumgunleri_mdb.zip (24 kb.)


Serkan ŞAHİNOĞLU
(Chip Dergisi, Haziran 2002)


http://BilgiTeknoloji.net