Bilgisayar Programcılığı dünyasında genellikle bir işi yapmanın birden fazla yolu vardır. Özellikle veritabanı tarafında bu yollar daha da fazla olabilir. Uygulama Geliştiriciler olarak yeni bir kod parçası yazacağımız zaman, genellikle bu yollardan en çok kullandığımız bir tanesini seçeriz ve onu kullanırız.
Ne var ki, seçtiğimiz yol herzaman en doğru yol olmayabilir. Sql‘de join‘li sorgulama yaparken, filtreleme için kullandığımız yöntem de bunlardan biri olabilir.
Bu yazımda join‘li sorgularda filtreleme için seçebileceğimiz iki yolu karşılaştıracağım. Karşılaştırma yapmak için kendi bilgisayarımda kurulu olan Sql Server 2008 üzerinde AdventureWorkd2008 R2 veritabanını kullandım.
Eğer birden fazla tablonun join‘ler ile ilişkilendirildiği bir sorgu yazıyorsak, sonuç kümesini filtreleme için kullanabileceğimiz iki yöntem vardır;
WHERE Filtreleme
Sonuç kümesinde olmasını/olmamasını istediğimiz kayıtları sorgunun WHERE cümlesinde tanımlarız. Şablon;
SELECT [TABLE1.ALANADLARI], [TABLE2.ALANADLARI] FROM [TABLE1] JOIN [TABLE2] ON [TABLE1.ALAN1] = [TABLE2.ALAN1] WHERE [TABLE1.ALAN2] = DEGER AND [TABLE2.ALAN2] = DEGER</pre> JOIN Filtreleme
Sonuç kümesinde olmasını/olmamasını istediğimiz kayıtları sorgunun JOIN cümlelerinde tanımlarız. Şablon;
SELECT [TABLE1.ALANADLARI], [TABLE2.ALANADLARI] FROM [TABLE1] JOIN [TABLE2] ON [TABLE1.ALAN1] = [TABLE2.ALAN1] AND [TABLE1.ALAN2] = DEGER AND [TABLE2.ALAN2] = DEGER
AdventureWorks2008 R2 veritabanında aşağıdaki iki sorguyu çalıştırdıktan sonra, performans analizini yapalım;
WHERE Filtreleme
SELECT * FROM Sales.SalesOrderHeader AS SOH WITH (NOLOCK) JOIN Sales.SalesOrderDetail AS SOD WITH (NOLOCK) ON SOH.SalesOrderID = SOD.SalesOrderID JOIN Sales.SalesOrderHeaderSalesReason AS SOHSR WITH (NOLOCK) ON SOHSR.SalesOrderID = SOH.SalesOrderID JOIN Sales.SalesReason AS SR WITH (NOLOCK) ON SOHSR.SalesReasonID = SR.SalesReasonID WHERE SOH.CustomerID > 15000 AND SOD.LineTotal > 2000 AND SR.SalesReasonID > 5
JOIN Filtreleme
SELECT * FROM Sales.SalesOrderHeader AS SOH WITH (NOLOCK) JOIN Sales.SalesOrderDetail AS SOD WITH (NOLOCK) ON SOH.SalesOrderID = SOD.SalesOrderID AND SOH.CustomerID > 15000 AND SOD.LineTotal > 2000 JOIN Sales.SalesOrderHeaderSalesReason AS SOHSR WITH (NOLOCK) ON SOHSR.SalesOrderID = SOH.SalesOrderID JOIN Sales.SalesReason AS SR WITH (NOLOCK) ON SOHSR.SalesReasonID = SR.SalesReasonID AND SR.SalesReasonID > 5
Her iki sorgu da çalıştıktan sonra 1321 satır geri döndürdü. Performans incelemesini üç alanda yapacağız;
SELECT DB_NAME(SP.DBID) AS VERITABANI, EST.TEXT AS SORGU, CPU, PHYSICAL_IO AS DISK_OKUMA, MEMUSAGE AS HAFIZA_KULLANIM FROM SYS.SYSPROCESSES AS SP CROSS APPLY SYS.DM_EXEC_SQL_TEXT(SP.SQL_HANDLE) AS EST Bu sorguyu çalıştırdıktan sonra benim test bilgisayarımda şu verileri elde ettim; * *VERITABANI :* **AdventureWorks2008R2** * *CPU :* **1965** * *DISK_OKUMA :* **211** * *HAFIZA_KULLANIM :* **2** * *VERITABANI :* **AdventureWorks2008R2** * *CPU :* **156** * *DISK_OKUMA :* **0** * *HAFIZA_KULLANIM :* **2** Gördüğünüz gibi kaynak kullanımı açısından (özellikle **Disk Okuma** ve **CPU Kullanımı** açısından) **JOIN Filtreleme**, **WHERE Filtreleme**den daha avantajlı. **JOIN Filtreleme** tabloları eşlerken filtrelenmiş verileri kullanıyor, **WHERE Filtreleme** ise, önce tabloları eşleştiriyor sonra filtreliyor. Özellikle çok kayıt bulunan/bulunacak olan tablolarınıza sorgu yazarken, JOIN Filtreleme'yi kullanmanızı tavsiye ederim.
Eğer uygulamanızda, Text-to-Speech (TTS) yeteneği olmasını istiyorsanız, .Net Framework içerisinde bunu yapmanızı sağlayacak sınıflar mevcut.
Projenizde Text-To-Speech sınıflarını kullanabilmek için, System.Speech.Synthesis namespace’indeki sınıflara erişebilmeniz gereklidir.
System.Speech.Synthesis namespace’ine, projenize System.Speech.dll assembly’yi referans olarak ekleyerek ulaşabilirsiniz.
Text-to-Speech kullanmak istediğiniz sınıfın bulunduğu kod dosyasının en üstünde bulunan using‘leri yazdığınız kısma;
using System.Speech.Synthesis;</pre> satırını ekleyiniz. Daha sonra yapmanız gereken, kodunuzun ilgili yerine aşağıdaki kod parçasını eklemek;
SpeechSynthesizer ss = new SpeechSynthesizer(); ss.Speak("Konuşmaya çevrilecek ve hoparlörden duyulacak metin");
Uygulama çalışırken, ilgili satıra geldiğinde, önce Speak() methoduna parametre olarak verdiğiniz string hoparlörden duyulur, sonra bir sonraki satırdan çalışmaya devam eder.
Eğer metnin konuşmaya çevrilme işleminin uzun sürdüğünü ve uygulamanıza “yavaşlık” kattığını düşünüyorsanız, aynı kodu aşağıdaki gibi yazabilirsiniz;
SpeechSynthesizer ss = new SpeechSynthesizer(); ss.SpeakAsync("Konuşmaya çevrilecek ve hoparlörden duyulacak metin"); *SpeakAsync()* method'u sayesinde, uygulama metnin konuşmaya çevrilmesi ve ses kartı aracılığıyla yayınlanması işlemini ayrı bir iş parçacığında gerçekleştirir. ***Not :** Speak() method'u ingilizce kelimeleri doğru olarak okuyor, fakat türkçe kelimeleri okuyamıyor.*
.Net Framework içerisinde string.ToUpper() ve string.ToLower() fonksiyonlarını kullanarak string değişkenin içeriğini BÜYÜK ve küçük harfe çevirebiliyoruz.
Metni Upper Case (Tümü Büyük Harfler) ve Lower Case (Tümü Küçük Harfler) formatlamak haricinde Title Case (Kelimelerin Baş Harfleri Büyük Gerisi Küçük) çevirmek yaygın kullanılan başka bir formattır.
string sınıfına bir Extension Method yazarak bu özelliğe sahip olmasını sağlayabiliriz. Extension Method‘u yazarken bilmemiz gereken ilk şey, ToTitleCase() method’unun TextInfo sınıfında yer aldığıdır. TextInfo sınıfına CultureInfo sınıfının CurrentCulture özelliğinden ulaşabiliriz.
public static class ExtensionManager { public static string ToTitleCase(this string Text) { return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(Text); } }</pre>
Artık projemiz içerisinde herhangi bir yerde string değişkenimizin ToTitleCase() method’unu kullanabiliriz.
private void frmMain_Load(object sender, EventArgs e) { string AdSoyad = "engin polat".ToTitleCase(); } Yukarıdaki kod parçası çalıştığı zaman, *AdSoyad* değişkeninin içeriği **Engin Polat** olacaktır. ***Not :** CultureInfo sınıfının CurrentCulture özelliğinden TextInfo sınıfının özelliklerine eriştiğimiz için, bilgisayarınızda kullandığınız dile göre sonuç farklılık gösterebilir.*
Geçen aylarda BilgeAdam‘da bir öğrencimin sorduğu soru geldi aklıma. Soru aslında çok basit, fakat farkettim ki, internette bu konu ile ilgili pek kaynak yok. Bari kendim yazayım dedim. Soru şu;
Bir method’dan geriye true/false değer döndürmem lazım.
Method içerisine yazdığım kodları try-catch-finally blokları içerisine almam gerekiyor.
Eğer try içerisinde hata oluşmazsa, geriye true değer döndüreceğim. Hata oluşur ve kod catch bloğuna girerse false değer döndüreceğim.
Ama finally kod bloğunda yapmam gereken işler var. (Açık bağlantıları kapatmak, vs.)
Eğer try-catch içerisinde return ifadesini kullanırsam, finally bloğundaki kod çalışır mı?</pre> Sorunun doğru cevabı için;
Evet, eğer try-catch-finally ifadesinde try-catch içerisinde return ile method sonlandırılırsa bile, finally bloğundaki kod çalıştırılır.
Hemen bir örnek ile açıklayayım;
public bool OrnekMethod { try { /// Yapılacak işler /// Başarılı olursa; return true; } catch (Exception ex) { /// Hatayı kaydet, vs. return false; } finally { ///Açık kaynakların kapatılması, vs. işleri gerçekleştirilir } } Yukarıdaki örnek'te, **return** ifadesi ile method'dan çıkılmadan önce, **finally** bloğundaki işler gerçekleştirilecektir.
Sql Server‘da çalıştırılan her sorgu, aslında çalıştırılmadan önce derleme (compile) işlemine tabi tutulur. Bu derleme işlemi sonucunda Sql Server query plan denilen çıktıyı üretir.
Query Plan, query processor‘e (sorguyu işleyen birim), sorgunun ihtiyaç duyduğu veriler için veritabanında bulunan tablo ve index‘lere fiziksel olarak nasıl erişebileceğini söyler.
Fakat, query plan elde etmek için yapılan bu derleme işlemi, bazı sorgular için çok pahalı olabilir.
Sql Server, aynı sql sorgusunun defalarca çalıştırıldığı durumlarda, derleme işleminin yükünü azaltmak için, query plan cache denilen hafıza bölgesinde query plan‘ları önbellekler.
Query plan cache, önbellekleyeceği sorguları basit bir hash tablo‘da saklar. Hash tablo’nun iki alanı vardır, birinde sql sorgusunun kendisini, diğerinde derleme sonucu ortaya çıkan query plan‘ı saklar.
Sql Server, yeni bir sorgu çalıştıracağı zaman, ilk önce query plan cache‘te sorgunun query plan‘ı var mı diye bakar. Eğer bulursa, daha önce önbelleklenmiş bu query plan‘ı kullanır. Bulamazsa, ilk önce sorguyu yazım denetimine tabi tutar, sonra sorguyu derler ve oluşan query plan‘ı bu listeye ekler.
Query plan cache‘in getirdiği performans artışını ölçmek için, öncelikle Sql Server’daki query plan cache’i boşaltacağız;
DBCC FREEPROCCACHE</pre>
Dikkat : Bu komutu kullandığınızda, Sql Server üzerinde bulunan tüm query plan cache silinir. Veritabanı veya belli bir sorgu için temizleme mümkün değildir. Bu komutun Canlı Veritabanında (Production Database) kullanılmaması önerilir.
Şimdi Sql Server’ın sorguyu inceleme ve derleme işlemi için ne kadar vakit harcadığını bulmamız lazım. Yapmamız gereken, sorguyu çalıştırmadan önce aşağıdaki komutu çalıştırmak;
SET STATISTICS TIME ON
Şimdi sorgumuzu çalıştırabiliriz. Ben Sql Server 2008 için hazırlanmış örnek veritabanında (AdventureWorks 2008R2) aşağıdaki sorguyu çalıştırıyorum;
SELECT * FROM HumanResources.Employee WHERE BusinessEntityId IN (1, 2);
Sorgu sonucu;
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 12 ms. (2 row(s) affected) SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms.
Gördüğünüz gibi, sorgunun parse ve compile işlemine tabi tutulması 12 ms. sürdü. Sorgunun çalıştırılması ise 1 ms. sürdü.
Aynı sorguyu tekrar çalıştırırsak, elde edeceğimiz sonuç;
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 1 ms. (2 row(s) affected) SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms. **Query plan cache** sayesinde, **parse** ve **compile** işlemi *12 ms.* yerine *1 ms.* sürdü. ***Not :** Sql Server'ın istatistik toplarken ulaşabileceği en düşük kesinlik süresi 1 ms.'dir. 1 ms.'den kısa süren işler için bile Sql Server 1 ms. raporlar.*
Senior Software Engineer, @Microsoft
Ada ve Ege'nin babası ;)
Makale Adedi: 484