SQL Pencere Fonksiyonları Nasıl Kullanılır? Örneklerle Adım Adım
Beceri rehberi

SQL Pencere Fonksiyonları Nasıl Kullanılır? Örneklerle Adım Adım

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 6 dk okuma

SQL pencere fonksiyonlarını OVER, PARTITION BY, ROW_NUMBER, LAG ve çerçeve tanımıyla örneklerle öğrenin; sık hataları ve çözüm kalıplarını görün.

Pencere fonksiyonu nedir, GROUP BY'dan farkı ne?

Pencere (window) fonksiyonu, her satır için o satırın çevresindeki bir satır kümesi üzerinde hesaplama yapar, ama satırları tek bir sonuca indirgemez. GROUP BY ile toplama yaptığınızda her grup tek satıra çöker ve ayrıntı kaybolur. Pencere fonksiyonunda ise ayrıntı satırları olduğu gibi kalır; yanlarına bir sütun olarak toplam, sıra numarası ya da bir önceki satırın değeri eklenir.

Bu fark, analitik sorguların en sık ihtiyacını karşılar: "Her satışı, kendi bölgesinin toplamıyla yan yana göster" ya da "Her müşterinin siparişlerini tarihe göre sırala ve bir öncekiyle farkı hesapla". GROUP BY ile bu sorular ancak alt sorgu ve self-join zinciriyle çözülürdü. Pencere fonksiyonuyla tek bir SELECT içinde, daha okunur biçimde yazılır.

OVER, PARTITION BY ve ORDER BY: sözdiziminin parçaları

Her pencere fonksiyonu OVER anahtar sözcüğüyle çalışır. Parantezin içinde pencereyi tanımlarsınız: • PARTITION BY: Satırları bağımsız gruplara böler. Örneğin PARTITION BY bolge dediğinizde her bölge kendi içinde ayrı bir pencere olur. Yazılmazsa tüm sonuç kümesi tek pencere sayılır. • ORDER BY: Pencere içindeki satır sırasını belirler. Sıralama, sıra numarası ve önceki sonraki satır fonksiyonları için anlamın kendisidir; toplamlarda ise birikimli (running) hesabı doğurur. • Çerçeve (frame): ROWS BETWEEN gibi ifadelerle pencerenin hangi satırları kapsadığını daraltır; örneğin başlangıçtan geçerli satıra kadar ya da son birkaç satır.

Temel kalıp şu biçimdedir: SELECT customer, order_date, amount, SUM(amount) OVER (PARTITION BY customer ORDER BY order_date) AS running_total FROM orders. Sorgu, her müşteri için siparişleri tarih sırasıyla dizer ve her satırda o ana kadarki toplamı gösterir. Burada GROUP BY yoktur; satır sayısı değişmez.

Sıralama fonksiyonları: ROW_NUMBER, RANK, DENSE_RANK

Bu fonksiyonların hepsi pencere içinde sıra numarası üretir, farkları eşit değerlerde ortaya çıkar. ROW_NUMBER eşit olsa bile her satıra benzersiz sıra verir. RANK eşit değerlere aynı sırayı verir ve sonraki sırayı atlar. DENSE_RANK eşit değerlere aynı sırayı verir ama atlama yapmaz.

En sık kullanım "her grubun ilk kaydı" sorusudur. Örneğin her departmanın en yüksek maaşlı çalışanını bulmak için ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) sütunu bir alt sorguda ya da CTE içinde üretilir, dıştaki sorgu da bu sütunu bire eşitleyerek filtreler. Pencere fonksiyonları WHERE koşulunda doğrudan kullanılamaz, çünkü sorgu işlenirken WHERE aşaması pencere hesabından önce çalışır. Bu yüzden iç içe yapı gerekir.

Mükerrer kayıt temizliği de aynı kalıba dayanır: Aynı e-postaya sahip satırları PARTITION BY email ile numaralandırıp yalnızca bir numaralı satırı tutarsınız. Hangi kaydın kalacağı ORDER BY ile açıkça belirlenir; örneğin en yeni güncelleme.

LAG ve LEAD: önceki ve sonraki satıra bakmak

LAG, aynı penceredeki bir önceki satırın değerini; LEAD ise bir sonraki satırın değerini getirir. Zaman serilerinde dönemler arası değişimi hesaplamak için vazgeçilmezdir. Aylık ciro tablosunda her ayın yanına bir önceki ayın cirosunu LAG ile getirir, farkı ve yüzdesel değişimi bir sonraki sütunda hesaplarsınız.

İlk satırın öncesi olmadığı için LAG sonucu boş (NULL) gelir; bu beklenen davranıştır. İsterseniz fonksiyona varsayılan bir değer verebilirsiniz. Sınırları unutmak yaygın hatadır: Fark hesabında NULL ile işlem yapmak sonucu da NULL yapar, raporda boş satır olarak görünür.

Başka bir kullanım, olaylar arasındaki süreyi bulmaktır. Bir müşterinin ardışık siparişleri arasındaki gün farkı, LAG ile bir önceki sipariş tarihini getirip tarih çıkararak elde edilir. Bu, tekrar satın alma döngüsü gibi metriklerin temelini oluşturur.

Birikimli toplam, hareketli ortalama ve çerçeve tanımı

Toplama fonksiyonları (SUM, AVG, COUNT, MIN, MAX) OVER ile birlikte kullanıldığında pencere fonksiyonuna dönüşür. ORDER BY eklendiğinde varsayılan çerçeve genellikle başlangıçtan geçerli satıra kadar olandır; bu da birikimli toplamı verir. Hareketli ortalama için çerçeveyi açıkça daraltırsınız: son birkaç satır ve geçerli satır, ROWS BETWEEN ... PRECEDING AND CURRENT ROW sözdizimiyle belirtilir.

ROWS ile RANGE arasındaki fark inceliklidir. ROWS fiziksel satır sayısına, RANGE ise ORDER BY değerinin eşitliğine bakar; aynı tarihe sahip birden çok satır varsa RANGE bunların hepsini aynı anda pencereye alır ve birikimli toplam beklediğinizden farklı çıkabilir. Sonuç beklenmedikse önce çerçeve tanımına bakın.

FIRST_VALUE ve LAST_VALUE gibi fonksiyonlar çerçeveye duyarlıdır. LAST_VALUE'nun varsayılan çerçevede geçerli satırı döndürmesi, yeni başlayanları en çok şaşırtan davranışlardan biridir; pencerenin sonunu istiyorsanız çerçeveyi UNBOUNDED FOLLOWING'e kadar genişletmek gerekir. NTILE ise sıralı satırları eşit büyüklükte dilimlere ayırır; müşterileri harcamaya göre çeyreklere bölmek gibi segmentasyonlarda kullanılır.

Sık yapılan hatalar ve performans notları

Sık karşılaşılan hatalar şunlardır: • Pencere fonksiyonunu WHERE içinde kullanmaya çalışmak. Çözüm: CTE ya da alt sorgu ile sonucu önce üretmek, sonra filtrelemek. • ORDER BY yazmayı unutmak. Sıralama ve LAG gibi fonksiyonlarda ORDER BY olmadan sonuç belirsizdir ve motorlar arasında değişebilir. • Eşit değerlerde ROW_NUMBER ile RANK'i karıştırmak. Beraberlik önemliyse RANK ya da DENSE_RANK, benzersiz tek kayıt lazımsa ROW_NUMBER tercih edilir; ORDER BY'a ek bir ayırıcı sütun eklemek sonucu deterministik yapar. • Her pencere için ayrı sıralama yaptırıp maliyeti artırmak. Aynı PARTITION BY ve ORDER BY'ı paylaşan fonksiyonlar çoğu motorda tek sıralamayla çalışır; farklı pencere tanımlarını gereksiz çoğaltmayın. Tekrarlanan pencere tanımı için WINDOW maddesiyle ad vermek hem okunurluğu hem sürdürülebilirliği iyileştirir (desteği veritabanına göre değişir). • Büyük tablolarda PARTITION BY sütununun dizinsiz olması. Sıralama bellek ve disk maliyeti doğurur; yürütme planına bakmak için EXPLAIN kullanın.

Veritabanı motorları arasında küçük farklar vardır. PostgreSQL, SQL Server, Oracle ve MySQL pencere fonksiyonlarını destekler, ancak çerçeve seçenekleri ve ek fonksiyonlar farklılık gösterebilir. Üretim sorgusunu yazmadan önce kullandığınız sürümün belgelerini kontrol edin.

Nasıl öğrenilir: pratik yol

Önce GROUP BY, JOIN ve alt sorguda rahat olun; pencere fonksiyonları bu temelin üstüne oturur. Ardından küçük bir satış tablosunda şu sırayla ilerleyin: tek SUM OVER ile toplam, PARTITION BY ile bölge bazlı pay, ORDER BY ile birikimli toplam, ROW_NUMBER ile "her gruptan ilk kayıt", LAG ile dönemsel değişim. Her adımda aynı soruyu GROUP BY ve self-join ile de çözmeyi deneyin; farkı görünce fonksiyonların neden vazgeçilmez olduğunu daha iyi kavrarsınız.

Gerçek senaryolar öğrenmeyi pekiştirir: müşteri bazında ilk ve son sipariş, aylık ciroda geçen aya göre değişim, satış temsilcilerinin bölge içindeki sıralaması, oturum verisinde olaylar arası süre. Bu sorguları kendi iş verinizle tekrar yazmak, genel örneklerden çok daha kalıcı öğretir.

Bu beceriyi Mindset programlarında nerede çalışırsınız?

SQL ile Veri Sorgulama (İleri) eğitiminin müfredatında Pencere Fonksiyonları ile İleri Analiz modülü yer alır. Aynı programda Ortak Tablo İfadeleri (CTE) ve Recursive Sorgular, Alt Sorgular ve Korelasyon Teknikleri, Sorgu Performansı ve Yürütme Planı Analizi ile İleri Vaka Çalışmaları ve Analitik Modeller başlıkları bulunur.

Bu modüller, pencere fonksiyonlarını CTE ile birlikte kullanma ve sorgu planını okuma becerilerini tamamlar.

İlgili Mindset programı

SQL ile Veri Sorgulama (İleri)

Sık sorulanlar

Sık sorulan sorular

Pencere fonksiyonları ile GROUP BY ne zaman birlikte kullanılır?

Aynı sorguda birlikte kullanılabilirler. GROUP BY önce satırları gruplar, pencere fonksiyonu ise bu gruplanmış sonuç üzerinde çalışır. Örneğin aylık ciroyu GROUP BY ile hesaplayıp sonra LAG ile bir önceki ayla kıyaslayabilirsiniz. Bu durumda pencere fonksiyonunun girdisi ham satırlar değil, gruplanmış satırlardır ve SELECT içinde GROUP BY sütunlarıyla tutarlı olması gerekir.

ROW_NUMBER ile RANK arasındaki fark nedir?

ROW_NUMBER, eşit değerler olsa bile her satıra benzersiz bir sıra numarası verir. RANK ise eşit değerlere aynı sırayı verir ve sonraki sırayı boş bırakır. Tekrar eden kayıtları ayıklamak için ROW_NUMBER, yarışma tipi sıralama için RANK, boşluksuz sıra için DENSE_RANK uygundur.

Pencere fonksiyonu sonucunu neden WHERE ile filtreleyemiyorum?

Mantıksal işlem sırasında WHERE, pencere fonksiyonları hesaplanmadan önce çalışır; bu yüzden sonuç sütunu o aşamada henüz yoktur. Çözüm, pencere hesabını bir CTE ya da alt sorguda yapıp dış sorguda filtrelemektir. Bazı motorlar QUALIFY gibi kısayollar sunar, ancak bu yaygın standart değildir.

Pencere fonksiyonları performansı düşürür mü?

Maliyet, sıralama ve bölümleme işleminden gelir. Uygun dizin ve filtrelenmiş veri kümesiyle genellikle kabul edilebilir çalışır. Aynı sonucu self-join ve ilişkili alt sorguyla üretmek çoğu durumda daha pahalıdır. Kuşkunuz varsa yürütme planını inceleyin ve gereksiz sıralamaları azaltın.

Hangi veritabanları pencere fonksiyonlarını destekler?

PostgreSQL, SQL Server, Oracle, MySQL ve MariaDB'nin güncel sürümleri pencere fonksiyonlarını destekler. Çekirdek sözdizimi benzerdir, ancak desteklenen çerçeve türleri ve ek fonksiyonlar değişebilir. Kullandığınız motorun sürüm belgelerine bakmak, taşınabilirlik açısından önemlidir.

SQL Pencere Fonksiyonları: OVER, PARTITION BY, LAG