Güncel hayatta karşılaştığımız teknik problemler ne kadar karmaşık olursa olsun çözüme ulaşabilmek için ilgili konuyla ilgili bilgi birikimimizi sağlam temeller üzerine kurmuş olmamız gerekmektedir. Çünkü bir probleme müdahale edebilmek için arka planda kullandığımız sistemin nasıl davrandığını iyi kavramamız gerekir. Benzer şekilde SQL Server tarafında sık karşılaştığımız sorgularımızda ortaya çıkan performans gibi birçok karmaşık sorunu çözebilmek adına SQL Server’ın bizim sorgumuzu nasıl çalıştırdığını iyi bilmemiz gerekmektedir. Özellikle karmaşık yani birden fazla tablo ve deyim içeren sorgularımızın hangi sırada değerlendirildiği ve ne şekilde optimize edildiği gibi konuların detaylarını bilmek hem sorgu yazarken hem de daha önceden yazılmış bir sorgumuzda karşılaştığımız problemi çözerken bize yardımcı olacaktır. Bu nedenle kitabımın ilk bölümünde bir SQL sorgusunun yaşam döngüsünün temellerine değinmek istiyorum.
azdığımız bir sorgu çalıştırılmadan önce SQL Server tarafında sorgumuzda istediğimiz veriye en hızlı nasıl erişebileceğimiz sorusuna cevap veren ve bizim veriye erişim sıramızı belirleyen bileş “Query Optimizer” olarak adlandırılmaktadır. Daha açık bir ifadeyle karmaşık yani birden fazla tablo veya ifade içeren sorgularımızda öncelikle hangi tablodaki veriye erişerek sorgu sonucuna daha hızlı erişebilirim? Hangi tablo üzerindeki hangi indeksi kullanarak istenen veriye daha hızlı erişebilirim? Hatta bazı durumlarda biz belirtsek dahi iki tabloyu ne şekilde join edersem yani birleştirirsem daha hızlı veriye erişebilirim gibi soruları bizim yerimize soran ve sorgumuzun en optimum şekilde çalışması için bir çalışma planı(Execution Plan) oluşturan SQL Server bileşenidir. Şimdi adım adım karmaşık bir sorgu üzerinde SQL Server Query Optimizer aracının nasıl davrandığını inceleyelim.
Yukarıdaki sorgu ifademiz aslında karmaşık bir sorgunun adım adım SQL Server tarafında nasıl işlendiğini bize göstermektedir. Fakat dikkat ederseniz yazdığımız SQL kodları birçok programlama dilindeki kodlardan farklı olarak yazıldığı sırada değil daha önceden belirlenmiş olan bir yapıya göre kodlar işlenmektedir. Yukarıda da gördüğümüz gibi yazdığımız sorgularımız ilk cümle olan Select ifadesinden değil alt kısımda belirttiğimiz From ifadesinden itibaren çalıştırılmaya başlanır ve en üstte ilk yazdığımız Select ifadesi en son çalıştırılır.
Sorgumuzun her adımın çalışması ile SQL Server kendi içinde erişebileceği sanal bir tablo oluşturur. Her adımda oluşturulan tablo bir sonraki adım için kaynak tablo olarak kullanılır ve sonraki adımdaki işlemle birleştirilir. Oluşan bu sanal tablolara SQL Server dışında herhangi bir kullanıcının erişmesi mümkün değildir. Sadece sorgumuzun en son adımında oluşturulan tablo sorguyu çalıştıran uygulama ya da kullanıcı tarafında erişilebilir olmaktadır. Şimdi sorgumuzun çalıştırılırken hangi aşamalardan geçtiğini daha detaylı bir akış şeması üzerinde inceleyelim.
SQL Sorgusunun Anatomisi
- (1) FROM ifadesi ile sorgumuzda kullanılacak olan tablolar belirtilir. Bildiğimiz gibi sorgularımızda birden fazla tablo kullansak bile tek FROM ifadesi yazılır ve sorgu çalıştırılmaya bu adımdan başlanır. Aslında yukarıdaki akış şemasını incelediğimiz zaman sorgunun çalıştırılması sorgumuzun bir veya birden fazla tablo yani veri kaynağını kullanıp kullanmamasına göre şekillenmektedir. Birden fazla veri kaynağı kullandığımız durumlarda diğer veri kaynaklarının referans olarak belirtilen veri kaynağına hangi operatör ile bağlanılacağı bilgisine göre sorgumuzun nasıl çalıştırılacağına karar verilir. Örneğin çok sık kullandığımız Join işlemi operatör olarak seçilmişse ilk adım olarak iki veri kaynağı üzerinde kartezyen çarpımı(Cross Join) yapılarak bir sanal tablo üretilir. Daha sonra üretilen bu sanal tablo üzerinde eğer ON ifadesi ile bir Join işlemi için bir şart verilmişse bu şart uygulanıp yeni bir sanal tablo oluşturulur. Son işlem olarak eğer kullandığımız Join operatörü Inner değil Outer Join ise eşleşmeyen kayıtlar sana tabloya eklenerek ikinci adıma geçilir.
- (2) Where ifadesi ile bir şart belirtilmişse önceki adımda oluşturulmuş olan sanal tablo üzerindeki kayıtlara bu şart uygulanır ve şartı saplayan kayıtlar ile yeni bir sanal tablo oluşturulur. Oluşan bu tablo sorgumuzun yaşam döngüsündeki sonraki adım için kullanılacak olan referans tablodur. Bu tablo oluştuktan sonra sonraki yani üçüncü adıma geçilir.
- (3) Group By ifadesi ile en son elde edilen sanal tablomuzdaki kayıtlar belirlediğimiz kritere göre gruplanır ve her grup için tek satır veri olacak şekilde yeni bir sanal tablo oluşturulur.
- (4) Having ifadesi bildiğimiz gibi gruplama yaptığımız veri kümesi üzerinde bir filtre uygulama imkânı sunmaktadır. Having ifadesi ile bir önceki adımda oluşturulan sanal tablo üzerindeki kayıtlara belirlediğimiz koşula göre filtre uygulanır. Dikkat ederseniz buradaki koşul ifadesinin Sum, Count gibi bir gruplama fonksiyonu olması zorunlu değildir çünkü bir önceki adımda oluşturduğumuz sanal tablo üzerinde sadece gruplama fonksiyonları ile elde ettiğimiz değerler değil diğer değerler de mevcuttur. Fakat tam tersi durum yine koşul ifadesi ile filtreleme yapabileceğimiz ikinci adımdaki Where ifadesinde geçerli değildir. Yani ikinci adımda gruplama fonksiyonlarının sonuçlarını filtre için kullanamayız. Bunun sebebi ise hesaplama işleminin üçüncü adımda yapılması olup karşılaştırma işlemi sadece bu adımdan sonra mümkündür. Yukarıda yaptığımız detaylı açıklama da sorgularımızda koşul yazarken ne zaman Where ne zaman Having ifadesini kullanabileceğimizi göstermektedir.
- (5) Select ifadesi kullanıcıya gösterilecek olan kolonların listesini belirtmektedir. Aslında ilk olarak yazılmasına rağmen bu komut en son işlenmektedir. Select komutu işlenirken referans olarak bir önceki adımda oluşturulan sanal tabloyu kullanır. Bu tablo üzerinde öncelikle istenen kolonlar listelenir ve eğer varsa hesaplama işlemleri yapılarak yeni bir sanal tablo oluşturulur. Eğer DISTINCT ifadesi ile belirtilmişse ikinci adım olarak oluşturulan bu tablo üzerindeki mükerrer yani tekrar eden kayıtlar elenip yeni bir sanal tablo oluşturulur. Son adım olarak TOP ifadesi belirtilmişse bir önceki adımda oluşturulan sanal tablodan belirtilen sayıda ya da yüzde ifadesi ile belirtilen kayıt yeni bir sanal tabloya aktarılır. Ek bir bilgi olarak TOP ifadesi çalışırken sorgumuzda Order by kullanılıp kullanılmadığını kontrol eder ve eğer varsa bu şekilde sıralayarak kayıtlar üzerinde sınırlama yapar.
- (6) (Order By) bu ifadeyle önceki adımda oluşturulan sanal tablo üzerinde belirtilen şekilde sıralama yapılır. Yapılan sıralama sonucunda artık kullanıcıya gösterilecek olan sonuca erişmiş oluruz. Ayrıca dikkat edilmesi gereken bir diğer nokta bir bu adıma kadar yapılan işlemlerde tablodaki kolonlarımız gerçek isimleri ile ifade ediliyordu. Fakat Order by öncesi çalışan Select adımında eğer bir Alias yani takma isim kullanmışsak bunu order by ifadesinde de kullanabiliriz.