MySQL 8.0 Pencere Fonksiyonları Rehberi

MySQL 8.0, pencere fonksiyonlarını (window functions) yerleşik olarak sunar. Bu rehber; sürüm kapsamını, temel sözdizimini, gerçek sorgu kalıplarını v...

MySQL 8.0 Pencere Fonksiyonları Rehberi

MySQL 8.0, pencere fonksiyonlarını (window functions) yerleşik olarak sunar. Bu rehber; sürüm kapsamını, temel sözdizimini, gerçek sorgu kalıplarını ve sık yapılan hataları orta seviye geliştiriciler için adım adım ele alır.

Önkoşullar ve Sürüm Kapsamı

  • MySQL 8.0 veya üzeri bir sunucu gerekir. Pencere fonksiyonları 8.0 öncesi sürümlerde (5.7 dahil) desteklenmez; bu sürümlerde OVER sözdizimi hata verir.
  • MariaDB farklı bir ürün ve sürüm çizgisine sahiptir; burada yalnızca MySQL 8.0 davranışı esas alınır.
  • Örnekler ORDER BY ve GROUP BY ile temel düzeyde rahat olan bir okuyucuyu hedefler.

Pencere Fonksiyonu Nedir?

Pencere fonksiyonu, satırları tek bir çıktı satırına indirgemeden, her satır için bir "pencere" (satır kümesi) üzerinden hesaplama yapar. GROUP BY'dan temel farkı budur: gruplama satırları birleştirirken, pencere fonksiyonu satırları korur ve yanlarına hesaplanmış bir değer ekler.

Sözdiziminin çekirdeği şudur:

fonksiyon(...) OVER (
  [PARTITION BY ...]
  [ORDER BY ...]
  [frame_clause]
)
  • PARTITION BY: Satırları bağımsız gruplara ayırır. Belirtilmezse tüm sonuç kümesi tek bir pencere olur.
  • ORDER BY: Pencere içindeki satır sırasını belirler; sıralamaya bağlı fonksiyonlar için kritiktir.
  • frame_clause: Hesaplamanın hangi satır aralığında yapılacağını tanımlar (örneğin ROWS BETWEEN ... AND ...).

Adım 1: Örnek Tablo ve Veri

Aşağıdaki tabloyu ve verileri kendi ortamınızda oluşturup deneyebilirsiniz:

CREATE TABLE satislar (
  id          INT PRIMARY KEY,
  bolge       VARCHAR(20) NOT NULL,
  temsilci    VARCHAR(50) NOT NULL,
  ay          DATE NOT NULL,
  tutar       DECIMAL(10,2) NOT NULL
);

INSERT INTO satislar (id, bolge, temsilci, ay, tutar) VALUES
  (1, 'Ege',    'Ayşe', '2024-01-01', 1000.00),
  (2, 'Ege',    'Ayşe', '2024-02-01', 1500.00),
  (3, 'Ege',    'Mehmet','2024-01-01',  800.00),
  (4, 'Ege',    'Mehmet','2024-02-01', 1200.00),
  (5, 'Marmara','Zeynep','2024-01-01', 2000.00),
  (6, 'Marmara','Zeynep','2024-02-01', 1800.00);

Adım 2: Sıralama Fonksiyonları

Sıralama fonksiyonları bir pencere içindeki satırlara sıra numarası atar. En sık kullanılan üçü:

  • ROW_NUMBER(): Eşit değerlerde bile her satıra benzersiz, artan bir numara verir.
  • RANK(): Eşit değerlere aynı sırayı verir; sonraki sırada boşluk bırakır (1, 1, 3...).
  • DENSE_RANK(): Eşit değerlere aynı sırayı verir; boşluk bırakmaz (1, 1, 2...).
SELECT
  bolge,
  temsilci,
  ay,
  tutar,
  ROW_NUMBER() OVER (PARTITION BY bolge ORDER BY tutar DESC) AS sira,
  RANK()       OVER (PARTITION BY bolge ORDER BY tutar DESC) AS rnk,
  DENSE_RANK() OVER (PARTITION BY bolge ORDER BY tutar DESC) AS yogun_rnk
FROM satislar
ORDER BY bolge, tutar DESC;

Beklenen davranış: Her bölge kendi içinde ayrı numaralanır. Aynı tutara sahip iki satır varsa ROW_NUMBER onlara farklı numara verirken, RANK ve DENSE_RANK aynı numarayı verir; RANK sonraki numarada atlama yapar, DENSE_RANK yapmaz.

Adım 3: Toplulaştırma Fonksiyonlarını Pencere Olarak Kullanma

SUM, AVG, COUNT, MIN, MAX gibi toplulaştırma fonksiyonları OVER ile birlikte kullanıldığında satırları korur. Bu, "her satırın yanında grubun toplamı" gibi bir çıktı üretir.

SELECT
  bolge,
  temsilci,
  ay,
  tutar,
  SUM(tutar) OVER (PARTITION BY bolge) AS bolge_toplam,
  ROUND(tutar / SUM(tutar) OVER (PARTITION BY bolge) * 100, 2) AS bolge_payi_yuzde
FROM satislar
ORDER BY bolge, tutar DESC;

Beklenen davranış: bolge_toplam her bölge için sabit bir değerdir; bolge_payi_yuzde ise o satırın bölge toplamına oranını verir. Satır sayısı değişmez.

Adım 4: Kümülatif Toplam ve Pencere Çerçevesi

Varsayılan çerçeve, ORDER BY varlığında "başlangıçtan geçerli satıra kadar" anlamına gelir. Bu, kümülatif (running) toplam için doğrudan kullanılabilir:

SELECT
  bolge,
  temsilci,
  ay,
  tutar,
  SUM(tutar) OVER (
    PARTITION BY bolge, temsilci
    ORDER BY ay
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS kumulatif_tutar
FROM satislar
ORDER BY bolge, temsilci, ay;

Beklenen davranış: Her temsilci için ay bazında artan bir kümülatif toplam üretilir. ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ifadesi açıkça yazıldığında, varsayılan çerçeveye bağımlı kalmazsınız.

Çerçeve seçimi önemlidir: ROWS fiziksel satır sayısına göre çalışır; RANGE ise ORDER BY sütunundaki değer eşitliğine göre çalışır. Eşit değerlerin bulunduğu verilerde RANGE beklenmedik toplamlar üretebilir; bu yüzden niyetinizi ROWS ile netleştirmek çoğu durumda daha güvenlidir.

Adım 5: Önceki/Sonraki Satıra Erişim

LAG ve LEAD, geçerli satıra göre önceki veya sonraki satırın değerini getirir. Fark ve değişim hesaplarında kullanışlıdır.

SELECT
  bolge,
  temsilci,
  ay,
  tutar,
  LAG(tutar)  OVER (PARTITION BY bolge, temsilci ORDER BY ay) AS onceki_tutar,
  tutar - LAG(tutar) OVER (PARTITION BY bolge, temsilci ORDER BY ay) AS degisim
FROM satislar
ORDER BY bolge, temsilci, ay;

Beklenen davranış: Her temsilcinin ilk ayında onceki_tutar ve degisim NULL olur; sonraki aylarda bir önceki aya göre fark hesaplanır. LAG/LEAD için isteğe bağlı bir ofset ve varsayılan değer de verilebilir (örneğin LAG(tutar, 1, 0)), ancak varsayılanın NULL olduğunu unutmayın.

Adım 6: Grup İçinde İlk/Son Satırı Seçme

"Her grubun en yüksek tutarlı satırını getir" gibi sorular için yaygın kalıp, pencere fonksiyonunu bir alt sorguda kullanıp dışarıda filtrelemektir:

SELECT bolge, temsilci, ay, tutar
FROM (
  SELECT
    bolge, temsilci, ay, tutar,
    ROW_NUMBER() OVER (PARTITION BY bolge ORDER BY tutar DESC) AS sira
  FROM satislar
) AS t
WHERE sira = 1;

Beklenen davranış: Her bölge için en yüksek tutarlı tek satır döner. Bu kalıp, GROUP BY ile "en yüksek tutar"ı bulup sonra eşleştirmekten genellikle daha okunaklıdır.

Sık Yapılan Hatalar

  • Pencere fonksiyonunu WHERE içinde kullanmak. Pencere fonksiyonları WHERE aşamasından sonra hesaplanır; bu nedenle doğrudan WHERE sira = 1 yazılamaz. Çözüm: alt sorgu veya WITH (CTE) kullanıp dış katmanda filtrelemek.
  • ORDER BY'ı unutmak. ROW_NUMBER, RANK, LAG, LEAD gibi fonksiyonlar sıralamaya bağlıdır; ORDER BY verilmezse sonuç belirsizdir.
  • PARTITION BY ile GROUP BY'ı karıştırmak. Pencere fonksiyonu satırları birleştirmez; gruplama bekliyorsanız GROUP BY gerekir.
  • Çerçeve türünü yanlış seçmek. Eşit ORDER BY değerlerinde RANGE ve ROWS farklı sonuç verir. Niyetinizi açıkça belirtin.
  • Aynı sorguda birden çok farklı pencere tanımlamak. Her OVER bloğu ayrı bir pencere tanımıdır; sık tekrarlanan tanımları WINDOW yan tümcesiyle adlandırarak okunabilirliği artırabilirsiniz.

Performans ve Güvenlik Notları

  • Pencere fonksiyonları genellikle sıralama ve ara sonuç gerektirir. PARTITION BY ve ORDER BY sütunlarındaki indeksler, uygun olduğunda planı iyileştirebilir; ancak her sorgu için indeks kullanımı garanti değildir. Gerçek planı EXPLAIN ile doğrulayın.
  • Pencere fonksiyonları büyük sonuç kümelerinde bellek ve geçici tablo kullanımını artırabilir. Dar bir WHERE filtresiyle önce veriyi küçültmek çoğu durumda faydalıdır.
  • Güvenlik açısından pencere fonksiyonları SQL enjeksiyonuna karşı kendiliğinden koruma sağlamaz. Kullanıcı girdisi sorguya parametre olarak bağlanmalı; PARTITION BY/ORDER BY gibi yapısal parçalar asla doğrudan dizge birleştirmeyle eklenmemelidir.
  • Pencere fonksiyonları, uygulama katmanındaki yetkilendirme mantığının yerini tutmaz. Satır düzeyinde erişim kontrolü gerekiyorsa bunu ayrıca uygulayın.

Özet

MySQL 8.0 pencere fonksiyonları; sıralama, kümülatif toplam, önceki/sonraki satır erişimi ve grup içi seçim gibi işleri tek sorguda, satırları koruyarak çözmenizi sağlar. Doğru sonuç için PARTITION BY, ORDER BY ve çerçeve tanımını bilinçli seçin; pencere fonksiyonlarını filtrelemek için alt sorgu veya CTE kullanın. Sürüm kapsamını ve plan davranışını EXPLAIN ile doğrulamak, üretim sorgularında beklenmedik sonuçların önüne geçer.

İLGİLİ İÇERİKLER

Bunları da Okumak İsteyebilirsiniz