İçeriğe geç
Educora
İleri22 dk13 / 22

Pencere fonksiyonları

Hiçbir satırı kaybetmeden sıralama yap, gruplar içinde numara ver, kümülatif toplam hesapla ve önceki satırla karşılaştır: ROW_NUMBER, RANK, DENSE_RANK, SUM() OVER, PARTITION BY, LAG ve LEAD.

Kendini test et
Bu derste öğreneceklerin
  • OVER (...) ifadesinin GROUP BY ifadesinden farkını açıklamak
  • ROW_NUMBER, RANK ve DENSE_RANK ile sıralama yapmak, PARTITION BY ile gruplar içinde sıralamak
  • SUM() OVER (ORDER BY ...) ile kümülatif toplam, LAG ve LEAD ile komşu satırlarla karşılaştırma hesaplamak

Öğretmen her ders için bir liste istiyor: her öğrencinin puanı, yanında da dersin ortalaması ve öğrencinin o dersteki sırası. GROUP BY bunu yapamaz; grubu tek bir satıra indirir ve adlar kaybolur. Pencere fonksiyonları ise her satırı yerinde tutar ve ona komşu satırlardan hesaplanan bir değer ekler. Sıralamalar, kümülatif toplamlar, “geçen aya göre değişim”: analitiğin büyük bölümü bu araçla yazılır.

OVER: satırlardan bir “pencere”

Tanım
Pencere fonksiyonu

Her satır için o satırla ilişkili bir satır kümesi, yani pencere üzerinden hesaplanan fonksiyon. Yazımı: fonksiyon(...) OVER (PARTITION BY ... ORDER BY ...). PARTITION BY pencereyi gruplara böler, ORDER BY ise pencere içindeki sırayı belirler.

Sıradan bir toplama fonksiyonunun arkasına OVER yazdığın anda o bir pencere fonksiyonuna dönüşür. Aşağıda her kaydın yanında kendi dersinin ortalaması görünür: satırlar birleştirilmez, ortalama ise her ders için ayrı hesaplanır.

SQL
SELECT e.course_id, e.student_id, e.score,
       ROUND(AVG(e.score) OVER (PARTITION BY e.course_id), 1) AS course_avg
FROM enrollments AS e
WHERE e.course_id IN (1, 3)
ORDER BY e.course_id, e.score DESC;
▸ Beklenen çıktı
course_id | student_id | score | course_avg
1 | 11 | 97 | 87.3
1 | 1 | 92 | 87.3
1 | 5 | 85 | 87.3
1 | 2 | 75 | 87.3
3 | 11 | 94 | 80
3 | 2 | 81 | 80
3 | 6 | 77 | 80
3 | 4 | 68 | 80
GROUP BY course_id burada iki satır verirdi; pencere fonksiyonu ise sekiz satırın hepsini tutar.

ROW_NUMBER, RANK ve DENSE_RANK

Üç sıralama fonksiyonu yalnızca eşit değerlerde farklılaşır. Öğrencileri yaşa göre sıralayalım: üç kişi 17, üç kişi 16 yaşında. ROW_NUMBER eşitliğe bakmaz ve 1, 2, 3 verir; bu yüzden sonucun sabit olması için benzersiz ikinci bir sıralama sütunu (id) ekliyoruz.

SQL
SELECT first_name, age,
       ROW_NUMBER() OVER (ORDER BY age DESC, id) AS row_num,
       RANK()       OVER (ORDER BY age DESC)     AS rnk,
       DENSE_RANK() OVER (ORDER BY age DESC)     AS dense
FROM students
ORDER BY age DESC, id
LIMIT 7;
▸ Beklenen çıktı
first_name | age | row_num | rnk | dense
Elvin | 17 | 1 | 1 | 1
Tural | 17 | 2 | 1 | 1
Fidan | 17 | 3 | 1 | 1
Murad | 16 | 4 | 4 | 2
Rəşad | 16 | 5 | 4 | 2
Səbinə | 16 | 6 | 4 | 2
Aysel | 15 | 7 | 7 | 3
FonksiyonEşitlikte17, 17, 16 için
ROW_NUMBER()her satıra ayrı bir numara verir1, 2, 3
RANK()aynı sıra, ardından boşluk1, 1, 3
DENSE_RANK()aynı sıra, boşluk yok1, 1, 2

PARTITION BY: her grubun kendi sıralaması

PARTITION BY course_id her dersi ayrı bir pencereye dönüştürür ve numaralandırma her derste 1'den başlar. “Her dersin en iyi öğrencisi” sorusu böyle çözülür: önce sıraları bir CTE içinde hesaplar, sonra pos = 1 olan satırları bırakırız.

SQL
WITH ranked AS (
  SELECT c.title, s.first_name, e.score,
         RANK() OVER (PARTITION BY e.course_id ORDER BY e.score DESC) AS pos
  FROM enrollments AS e
  JOIN students AS s ON s.id = e.student_id
  JOIN courses  AS c ON c.id = e.course_id
)
SELECT title, first_name, score
FROM ranked
WHERE pos = 1
ORDER BY title;
▸ Beklenen çıktı
title | first_name | score
Algebra | Fidan | 97
English B1 | Leyla | 90
Geometry | Leyla | 95
Mechanics | Fidan | 94
Organic Chemistry | Elvin | 72
Python Basics | Rəşad | 99
World History | Nigar | 93

Kümülatif toplam: SUM() OVER (ORDER BY ...)

OVER içine ORDER BY yazıldığında pencere “baştan bu satıra kadar” olur. Bu yüzden SUM kümülatif toplam verir: her ayın geliri önceki ayların toplamına eklenir. Aşağıda önce aylık geliri bir CTE ile hesaplıyor, sonra onun üzerinde bir pencere fonksiyonu çalıştırıyoruz.

SQL
WITH monthly AS (
  SELECT SUBSTR(o.order_date, 1, 7) AS month,
         ROUND(SUM(o.quantity * p.price), 2) AS revenue
  FROM orders AS o
  JOIN products AS p ON p.id = o.product_id
  GROUP BY month
)
SELECT month, revenue,
       ROUND(SUM(revenue) OVER (ORDER BY month), 2) AS running_total
FROM monthly
ORDER BY month;
▸ Beklenen çıktı
month | revenue | running_total
2025-01 | 1691 | 1691
2025-02 | 963.99 | 2654.99
2025-03 | 124.98 | 2779.97
2025-04 | 935.99 | 3715.96
2025-05 | 42 | 3757.96
2025-06 | 152.1 | 3910.06
2025-07 | 32 | 3942.06

Bir incelik var: ORDER BY varken varsayılan çerçeve RANGE kipindedir ve sıralama anahtarı aynı olan satırları (eşdeğer satırları) birlikte alır. İki sipariş aynı tarihteyse ikisi de o günün toplamının tamamını gösterir. Satır satır büyüyen bir toplam için benzersiz bir anahtar ekle ya da çerçeveyi açıkça yaz: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.

SQL
SELECT id, order_date, quantity,
       SUM(quantity) OVER (ORDER BY order_date)     AS by_range,
       SUM(quantity) OVER (ORDER BY order_date, id) AS by_row
FROM orders
WHERE order_date < '2025-05-01'
ORDER BY order_date, id;
▸ Beklenen çıktı
id | order_date | quantity | by_range | by_row
1 | 2025-01-15 | 1 | 3 | 1
2 | 2025-01-15 | 2 | 3 | 3
3 | 2025-02-02 | 20 | 23 | 23
4 | 2025-02-10 | 1 | 24 | 24
5 | 2025-03-05 | 1 | 25 | 25
6 | 2025-03-18 | 2 | 27 | 27
7 | 2025-04-09 | 1 | 31 | 28
8 | 2025-04-09 | 3 | 31 | 31
1 ve 2 numaralı siparişler aynı gün verilmiş: by_range ikisinde de 3 gösterir, by_row ise 1 ve 3.

LAG ve LEAD: komşu satırla karşılaştırma

LAG(x) penceredeki önceki satırın değerini, LEAD(x) ise sonraki satırın değerini döndürür. Bunlarla “geçen aya göre değişim” tek bir sorguyla hesaplanır. İlk ayın bir öncesi olmadığı için LAG orada NULL verir. LAG(x, 2) iki satır geriye bakar, LAG(x, 1, 0) ise NULL yerine 0 döndürür.

SQL
WITH monthly AS (
  SELECT SUBSTR(o.order_date, 1, 7) AS month,
         ROUND(SUM(o.quantity * p.price), 2) AS revenue
  FROM orders AS o
  JOIN products AS p ON p.id = o.product_id
  GROUP BY month
)
SELECT month, revenue,
       LAG(revenue) OVER (ORDER BY month) AS prev_revenue,
       ROUND(revenue - LAG(revenue) OVER (ORDER BY month), 2) AS change
FROM monthly
ORDER BY month;
▸ Beklenen çıktı
month | revenue | prev_revenue | change
2025-01 | 1691 | NULL | NULL
2025-02 | 963.99 | 1691 | -727.01
2025-03 | 124.98 | 963.99 | -839.01
2025-04 | 935.99 | 124.98 | 811.01
2025-05 | 42 | 935.99 | -893.99
2025-06 | 152.1 | 42 | 110.1
2025-07 | 32 | 152.1 | -120.1
Alıştırma

Ürünleri her kategori içinde fiyata göre sırala: en pahalı ürün 1. sırayı alsın, sıralarda boşluk olmasın. category, name, price ve price_rank sütunlarını kategoriye, sonra sıraya göre sıralı göster.

Alıştırma · SQL
SELECT category, name, price
       -- add price_rank here
FROM products
ORDER BY category, price DESC;
▸ Beklenen çıktı
category | name | price | price_rank
Accessories | Backpack | 55 | 1
Accessories | Water bottle | 12 | 2
Electronics | Laptop | 1450 | 1
Electronics | Smartphone | 899.99 | 2
Electronics | Monitor | 310 | 3
Electronics | Headphones | 120.5 | 4
Games | Chess set | 42 | 1
Home | Desk lamp | 34.99 | 1
Stationery | Pen set | 7.9 | 1
Stationery | Notebook | 3.2 | 2
Alıştırma

1, 2 ve 3 numaralı müşterilerin siparişleri için her müşterinin kendi sipariş numarasını (order_no: tarihe, sonra id değerine göre 1, 2, 3...) ve o müşterinin kümülatif ürün toplamını (running_qty) hesapla. customer_id, order_date, quantity, order_no, running_qty sütunlarını müşteriye ve sipariş numarasına göre sıralı göster.

Alıştırma · SQL
SELECT customer_id, order_date, quantity
       -- order_no and running_qty
FROM orders
WHERE customer_id IN (1, 2, 3)
ORDER BY customer_id, order_date, id;
▸ Beklenen çıktı
customer_id | order_date | quantity | order_no | running_qty
1 | 2025-01-15 | 1 | 1 | 1
1 | 2025-01-15 | 2 | 2 | 3
1 | 2025-07-07 | 10 | 3 | 13
2 | 2025-02-02 | 20 | 1 | 20
2 | 2025-06-30 | 1 | 2 | 21
3 | 2025-02-10 | 1 | 1 | 1
3 | 2025-05-21 | 1 | 2 | 2

Önemli noktalar

  • Pencere fonksiyonu satırları birleştirmez: her satıra OVER (...) penceresinden hesaplanan bir değer ekler.
  • PARTITION BY pencereyi gruplara böler, ORDER BY ise içindeki sırayı belirler.
  • Eşitlikte: ROW_NUMBER 1, 2, 3; RANK 1, 1, 3; DENSE_RANK 1, 1, 2.
  • SUM(x) OVER (ORDER BY ...) kümülatif toplam verir; aynı anahtarlı satırlarda ROWS çerçevesi ya da benzersiz bir anahtar gerekir.
  • LAG önceki, LEAD sonraki satırın değerini döndürür; pencere fonksiyonuna göre filtrelemek için CTE ya da alt sorgu gerekir.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Pencere fonksiyonu ile GROUP BY arasındaki temel fark nedir?