İçeriğe geç

SQL Server’da Pencere Fonksiyonları: Sıralama, LAG ve Kümülatif Toplam

GROUP BY satırları kaybettirir, pencere fonksiyonları korur. ROW_NUMBER, LAG, kümülatif toplam ve hareketli ortalamayı örneklerle anlatıyorum.

SQL Server güncellendi 25.10.2026 4 dk okuma Atılay Berber

Raporlama sorgularında en sık ihtiyaç duyulan şeylerden biri, satırları gruplayıp kaybetmeden yanlarına hesaplama eklemektir: sıra numarası, önceki ayın değeri, kümülatif toplam, hareketli ortalama. GROUP BY satırları tek satıra indirir; pencere fonksiyonları (OVER ifadesi) ise her satırı korur. Bu yazıda en çok kullandığım dört kalıbı örnekliyorum.

Örnek tablo

CREATE TABLE dbo.AylikSatis (
    Magaza   varchar(20),
    Ay       date,
    Tutar    decimal(18,2)
);

INSERT INTO dbo.AylikSatis VALUES
 ('Merkez', '2026-01-01', 120000), ('Merkez', '2026-02-01', 135000),
 ('Merkez', '2026-03-01', 128000), ('Merkez', '2026-04-01', 150000),
 ('Sahil',  '2026-01-01',  80000), ('Sahil',  '2026-02-01',  76000),
 ('Sahil',  '2026-03-01',  91000), ('Sahil',  '2026-04-01',  99000);

1. Sıralama: ROW_NUMBER, RANK, DENSE_RANK

Her mağazanın en yüksek satış yaptığı ayı bulmak için satırları mağaza bazında numaralandırıp birinci olanı alırız:

WITH Sirali AS (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY Magaza ORDER BY Tutar DESC) AS Sira
    FROM dbo.AylikSatis
)
SELECT Magaza, Ay, Tutar
FROM Sirali
WHERE Sira = 1;

ROW_NUMBER eşit değerlere de farklı numara verir. Eşitlerin aynı sırayı alması gerekiyorsa RANK (boşluk bırakır: 1,1,3) ya da DENSE_RANK (boşluk bırakmaz: 1,1,2) kullanın. Bu kalıp, tekrar eden kayıtları temizlerken (Sira > 1 olanları silmek) de çok işe yarar.

2. Önceki dönemle karşılaştırma: LAG

SELECT Magaza, Ay, Tutar,
       LAG(Tutar) OVER (PARTITION BY Magaza ORDER BY Ay) AS OncekiAy,
       Tutar - LAG(Tutar) OVER (PARTITION BY Magaza ORDER BY Ay) AS Fark
FROM dbo.AylikSatis
ORDER BY Magaza, Ay;

LAG bir önceki satırın değerini, LEAD bir sonrakini getirir. İkinci parametreyle kaç satır geri gidileceğini (LAG(Tutar, 12) ile geçen yılın aynı ayı gibi) belirtebilirsiniz. Self-join yazmaya gerek kalmaz.

3. Kümülatif toplam

SELECT Magaza, Ay, Tutar,
       SUM(Tutar) OVER (
           PARTITION BY Magaza
           ORDER BY Ay
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS KumulatifToplam
FROM dbo.AylikSatis;

ROWS ifadesini açıkça yazmanızı öneririm. Yazmazsanız SQL Server varsayılan olarak RANGE kullanır; aynı tarihte birden fazla satır varsa sonuç beklediğinizden farklı olabilir ve performans da genellikle daha düşüktür.

4. Hareketli ortalama

SELECT Magaza, Ay, Tutar,
       AVG(Tutar) OVER (
           PARTITION BY Magaza
           ORDER BY Ay
           ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
       ) AS UcAylikOrtalama
FROM dbo.AylikSatis;

Çerçeveyi (frame) değiştirerek 7 günlük, 12 aylık gibi istediğiniz pencereyi kurabilirsiniz. Mevsimsel dalgalanmaları yumuşatmak için raporlarda sık kullanılır.

Performans notları

  • PARTITION BY ve ORDER BY sütunlarına uygun bir indeks (örneğin (Magaza, Ay) INCLUDE (Tutar)) sıralama maliyetini ciddi şekilde düşürür.
  • Pencere fonksiyonları WHERE içinde doğrudan kullanılamaz; yukarıdaki gibi bir CTE ya da alt sorgu ile sarmalayın.
  • Aynı OVER tanımını tekrar tekrar yazmak yerine SQL Server 2022 ve sonrasında WINDOW ifadesiyle pencereye isim verebilir, gerektiğinde çerçeveyi ekleyerek genişletebilirsiniz. (LAG gibi fonksiyonlar çerçeve kabul etmediği için isimli pencereye çerçeveyi koymayın.)
-- SQL Server 2022+
SELECT Magaza, Ay, Tutar,
       LAG(Tutar) OVER w AS OncekiAy,
       SUM(Tutar) OVER (w ROWS UNBOUNDED PRECEDING) AS Kumulatif
FROM dbo.AylikSatis
WINDOW w AS (PARTITION BY Magaza ORDER BY Ay);

Sık yapılan hatalar

  • Eşitlikte belirsiz sıralama: ORDER BY sütununda eşit değerler varsa ROW_NUMBER her çalıştırmada farklı satırı birinci seçebilir. Sonucun tutarlı olması için sıralamaya benzersiz bir sütun (örneğin bir kimlik) ekleyin.
  • LAST_VALUE tuzağı: Çerçeve belirtilmezse varsayılan çerçeve geçerli satırda biter; bu yüzden LAST_VALUE çoğu zaman satırın kendi değerini döndürür. ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING yazın.
  • LAG’in ilk satırda NULL döndürmesi: Önceki satır olmadığında sonuç NULL olur. Üçüncü parametreyle varsayılan değer verebilirsiniz: LAG(Tutar, 1, 0).
  • Eksik ayları fark etmemek: LAG bir önceki satırı getirir, bir önceki ayı değil. Bir ay hiç satış yoksa karşılaştırma kayar; önce bir tarih tablosuyla boşlukları doldurun.

Sık sorulan sorular

Pencere fonksiyonu mu, GROUP BY mı?

Satır başına tek bir özet yeterliyse GROUP BY. Hem satırın kendisini hem de grup bazlı bir hesabı (sıra, toplam payı, önceki değer) aynı satırda görmek istiyorsanız pencere fonksiyonu.

Veriyi eşit gruplara bölmek için ne kullanılır?

NTILE(4) OVER (ORDER BY Tutar DESC) satırları dört eşit gruba ayırır; mağazaları ya da ürünleri çeyreklik dilimlere ayırmak için pratiktir.

İlgili yazılar

HABERDAR OL

Yeni rehberler, kategorilere ayrılmış tek bir e-postada.

Her pazartesi, o hafta yayımlanan yazılar konu başlıklarına göre derlenmiş olarak gelen kutunda. Yeni yazı yoksa e-posta da yok.

Onay e-postasındaki bağlantıya tıklamadan liste aktif olmaz. İstediğin an tek tıkla çıkabilirsin.

Faydalı bulduysan paylaş
LinkedIn'de paylaş X WhatsApp

2 yorum

Yorum yaz

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir