4.4. Pencere İşlevleri

Bir pencere işlevi, geçerli satırla bir şekilde ilişkili olan bir dizi tablo satırında bir hesaplama gerçekleştirir. Bu hesaplama türü, toplama işlevleriyle yapılabilecek hesaplamayla karşılaştırılabilir. Ancak, pencere işlevleri, penceresiz toplama çağrıların yaptığı gibi satırların tek bir çıktı satırında gruplandırılmasını sağlamaz. Bunun yerine, satırlar kendi kimliklerini korur. Perde arkasında, pencere işlevi, sorgu sonucunun geçerli tek bir satırından çok daha fazlasına erişebilir.

Her çalışanın (employee) maaşını (salary) kendi bölümündeki (depname) ortalama maaşla nasıl karşılaştıracağını gösteren bir örnek:

SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;

Çıktısı:

  depname  | empno | salary |          avg
-----------+-------+--------+-----------------------
 develop   |    11 |   5200 | 5020.0000000000000000
 develop   |     7 |   4200 | 5020.0000000000000000
 develop   |     9 |   4500 | 5020.0000000000000000
 develop   |     8 |   6000 | 5020.0000000000000000
 develop   |    10 |   5200 | 5020.0000000000000000
 personnel |     5 |   3500 | 3700.0000000000000000
 personnel |     2 |   3900 | 3700.0000000000000000
 sales     |     3 |   4800 | 4866.6666666666666667
 sales     |     1 |   5000 | 4866.6666666666666667
 sales     |     4 |   4800 | 4866.6666666666666667
(10 satır)

Çıktının ilk üç sütunu doğrudan empsalary tablosundan gelir ve tablodaki her satır için bir çıktı satırı vardır. Dördüncü sütun, geçerli satırla aynı depname değerine sahip tüm satırlardan elde edilen ortalamayı temsil eder. (Bu aslında, pencere olmayan avg toplamı ile aynı işlevdir, ancak OVER deyimi, bunun bir pencere işlevi olarak ele alınmasına ve pencere çerçevesi boyunca hesaplanmasına neden olur.)

Bir pencere işlevi çağrısı her zaman pencere işlevinin adını ve bağımsız değişkenlerini izleyen bir OVER deyimi içerir. Bu, onu normal bir işlevden veya pencere olmayan bir toplamdan sözdizimsel olarak ayıran şeydir. OVER deyimi, sorgu satırlarının pencere işlevi tarafından işlenmek üzere tam olarak nasıl bölüneceğini belirler. OVER içindeki PARTITION BY deyimi, satırları, PARTITION BY ifade(ler)iyle aynı değerlerini paylaşan gruplara veya bölümlere ayırır. Her satır için pencere işlevi, geçerli satırla aynı bölüme düşen satırlar arasında hesaplanır.

OVER içinde ORDER BY kullanarak satırların pencere işlevleri tarafından işlenme sırasını da denetim altına alabilirsiniz. (ORDER BY penceresinin çıktı satırlarının sırasına uyması bile gerekmez.) İşte bir örnek:

SELECT depname, empno, salary,
       rank() OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;

Çıktısı:

  depname  | empno | salary | rank
-----------+-------+--------+------
 develop   |     8 |   6000 |    1
 develop   |    10 |   5200 |    2
 develop   |    11 |   5200 |    2
 develop   |     9 |   4500 |    4
 develop   |     7 |   4200 |    5
 personnel |     2 |   3900 |    1
 personnel |     5 |   3500 |    2
 sales     |     1 |   5000 |    1
 sales     |     4 |   4800 |    2
 sales     |     3 |   4800 |    2
(10 satır)

Burada gösterildiği gibi, rank() işlevi, ORDER BY deyimi tarafından tanımlanan sırayı kullanarak, geçerli satırın bölümündeki her farklı ORDER BY değeri için sayısal bir sıra üretir. rank() işlevinin bağımsız değişkenleri yoktur, çünkü davranışı tamamen OVER deyimi tarafından belirlenir.

Bir pencere işlevi tarafından dikkate alınan satırlar, sorgunun FROM deyimi tarafından WHERE, GROUP BY ve varsa HAVING deyimleri ile elde edilen "hayali" bir tablonun satırlarıdır. Örneğin, WHERE koşulunu karşılamadığı için kaldırılan bir satırı herhangi bir pencere işlevi göremez. Bir sorgu, farklı OVER deyimleri kullanarak verileri farklı şekillerde dilimleyen birden çok pencere işlevi içerebilir, ancak bunların tümü bu "hayali" tablo tarafından tanımlanan aynı satır kümesinde hareket eder.

Satırların sıralaması önemli değilse, ORDER BY deyiminin atlanabileceğini zaten gördük. PARTITION BY deyimini de atlamak da mümkündür, bu durumda tüm satırları içeren tek bir bölüm olur.

Pencere işlevleriyle ilişkili önemli kavram daha vardır: her satır için, kendi bölümünde pencere çerçevesi adı verilen bir dizi satır vardır. Bazı pencere işlevleri, tüm bölme yerine yalnızca pencere çerçevesinin satırları üzerinde hareket eder. Öntanımlı olarak, ORDER BY belirtilirse, pencere çerçevesi bölümün başlangıcından geçerli satıra kadar olan tüm satırlardan ve takip eden satırlardan ORDER BY deyimine göre geçerli satırla eşleşen satırlardan oluşur. ORDER BY atlandığında, öntanımlı pencere çerçevesi bölümdeki tüm satırlardan oluşur.[67] İşte sum() işlevini kullanan bir örnek:

SELECT salary, sum(salary) OVER () FROM empsalary;

Çıktısı:

 salary |  sum
--------+-------
   5200 | 47100
   5000 | 47100
   3500 | 47100
   4800 | 47100
   3900 | 47100
   4200 | 47100
   4500 | 47100
   4800 | 47100
   6000 | 47100
   5200 | 47100
(10 satır)

Yukarıda, OVER deyiminde ORDER BY olmadığı için pencere çerçevesi, PARTITION BY olmadığı için tablonun tamamından oluşan bölme ile aynıdır; diğer bir deyişle, her toplam tablonun tamamından elde edilir ve böylece her çıktı satırı için aynı sonucu elde ederiz. Ancak bir ORDER BY deyimi eklersek çok farklı sonuçlar elde ederiz:

SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary;

Çıktısı:

 salary |  sum
--------+-------
   3500 |  3500
   3900 |  7400
   4200 | 11600
   4500 | 16100
   4800 | 25700
   4800 | 25700
   5000 | 30700
   5200 | 41100
   5200 | 41100
   6000 | 47100
(10 satır)

Maaşlar düşükten yükseğe sıralanırken, ikinci sütunda her maaş kendinden öncekilerin toplamına eklenerek gösterilir.

Pencere işlevlerine yalnızca SELECT listesinde ve sorgunun ORDER BY deyiminde izin verilir. GROUP BY, HAVING ve WHERE deyimleri gibi başka yerlerde yasaktır. Bunun nedeni, bu deyimlerin işlenmesinden sonra mantıksal olarak yürütülmeleridir. Ayrıca, pencere işlevleri, penceresiz toplu işlevlerden sonra yürütülür. Bu, bir pencere işlevinin bağımsız değişkenlerine bir toplu işlev çağrısını dahil etmenin geçerli olduğu, ancak bunun tersinin geçerli olmadığı anlamına gelir.

Pencere hesaplamaları yapıldıktan sonra satırları elemek veya gruplandırmak gerekirse, bir alt seçim kullanabilirsiniz. Örneğin:

SELECT depname, empno, salary, enroll_date
FROM
  (SELECT depname, empno, salary, enroll_date,
          rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
     FROM empsalary
  ) AS ss
WHERE pos < 3;

Yukarıdaki sorgu, iç sorguda sıralanan satırlardan ilk 3'ünü gösterir.

Bir sorgu birden çok pencere işlevi içerdiğinde, her biri için ayrı bir OVER deyimi yazmak mümkündür, ancak birkaç işlev için aynı pencereleme davranışı istenirse bu tekrarlanabilir ama hataya açıktır. Bunun yerine, her pencereleme davranışı bir WINDOW deyiminde adlandırılıp OVER deyiminde bu ada başvurulabilir. Örnek:

SELECT sum(salary) OVER w, avg(salary) OVER w
  FROM empsalary
  WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);

Pencere işlevleri hakkında daha ayrıntılı bilgi PostgreSQL belgelerindeki Window Function Calls, Window Functions, Window Function Processing bölümlerinde ve SELECT kılavuz sayfasında bulunabilir.



[67] Pencere çerçevesini başka şekillerde tanımlamak da mümkündür, ancak bu eğitim bunları kapsamamaktadır. Ayrıntılar Window Function Calls bölümünde bulunabilir.