İçeriğe geç
academia.sh

Ders 06 / 20

Sıralama İşlevleri

Satır numarası, sıra ve yoğun sıra işlevlerinin beraberlik davranışları, dilim ve yüzdelik dağılım işlevleri, bölüm içinde ilk N seçimi ve satır numarasıyla tekilleştirme.

İçindekiler

Önceki ders pencerenin üzerinde hesap yürüttü ama satırlara sıra vermedi. “En çok ödünç alan üç üye” gibi sorular ise önce bir sıra numarası ister; hemen ardından da bir karar gerektirir: eşit sayıya sahip iki üye aynı sırayı mı almalıdır, farklı mı? Bu soruya üç ayrı yanıt verilebilir ve SQL üçünü de ayrı işlev olarak tanımlar.

Bu ders, sıralama işlevlerini aynı veri üzerinde yan yana koyup farklarını gösterir ve sıralamanın belirlenimci olması için gereken koşulu kurar.

Üç İşlev, Üç Beraberlik Kararı

Üç işlev de pencereyi ORDER BY ile sıralar ve her satıra bir tam sayı verir. Ayrıldıkları nokta, sıralama anahtarında eşit değerli satırlarla ne yapıldığıdır:

  • ROW_NUMBER: eşlik tanımaz. Her satıra farklı bir numara verir; 1, 2, 3 diye sürer.
  • RANK: eşlere aynı sırayı verir, sonra atlar. İki satır ikinci olursa sonraki dördüncüdür.
  • DENSE_RANK: eşlere aynı sırayı verir, atlamaz. İki satır ikinci olursa sonraki üçüncüdür.

Üçü de aynı sorguda yazılabilir; ortak pencere WINDOW yan tümcesinde bir kez tanımlanır:

sqlite3 -box -header <<'SQL'
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT, sube TEXT);
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT, alis TEXT, iade TEXT);
INSERT INTO uye VALUES (1,'Ayse','Kadikoy'),(2,'Burak','Kadikoy'),(3,'Ceren','Uskudar'),
  (4,'Deniz','Uskudar'),(5,'Emre','Besiktas'),(6,'Fatma','Besiktas'),
  (7,'Gokhan','Kadikoy'),(8,'Hale','Uskudar');
INSERT INTO odunc VALUES
  (1,1,1,'2024-03-01','2024-03-15'),(2,3,1,'2024-03-01','2024-03-20'),
  (3,5,1,'2024-03-04','2024-03-18'),(4,7,1,'2024-03-11',NULL),
  (5,9,1,'2024-03-18','2024-03-29'),(6,2,2,'2024-03-01','2024-03-12'),
  (7,4,2,'2024-03-06','2024-03-25'),(8,6,2,'2024-03-11','2024-03-19'),
  (9,8,2,'2024-03-21',NULL),(10,1,3,'2024-03-04','2024-03-10'),
  (11,3,3,'2024-03-06','2024-03-27'),(12,10,3,'2024-03-13','2024-03-22'),
  (13,2,3,'2024-03-25',NULL),(14,5,4,'2024-03-04','2024-03-09'),
  (15,7,4,'2024-03-13','2024-03-26'),(16,4,4,'2024-03-20',NULL),
  (17,6,5,'2024-03-06','2024-03-14'),(18,9,5,'2024-03-11','2024-03-23'),
  (19,1,5,'2024-03-25','2024-03-28'),(20,8,6,'2024-03-04','2024-03-17'),
  (21,10,6,'2024-03-13','2024-03-21'),(22,3,6,'2024-03-20',NULL),
  (23,2,7,'2024-03-11','2024-03-16'),(24,5,7,'2024-03-18','2024-03-24'),
  (25,4,8,'2024-03-13','2024-03-24');

WITH sayim AS (
  SELECT u.ad, u.sube, COUNT(o.id) AS adet
  FROM uye u LEFT JOIN odunc o ON o.uye_id = u.id
  GROUP BY u.id, u.ad, u.sube
)
SELECT ad, adet,
       ROW_NUMBER() OVER s AS satir_no,
       RANK()       OVER s AS sira,
       DENSE_RANK() OVER s AS yogun_sira
FROM sayim
WINDOW s AS (ORDER BY adet DESC)
ORDER BY adet DESC, ad;
SQL
┌────────┬──────┬──────────┬──────┬────────────┐
│   ad   │ adet │ satir_no │ sira │ yogun_sira │
├────────┼──────┼──────────┼──────┼────────────┤
│ Ayse   │ 5    │ 1        │ 1    │ 1          │
│ Burak  │ 4    │ 2        │ 2    │ 2          │
│ Ceren  │ 4    │ 3        │ 2    │ 2          │
│ Deniz  │ 3    │ 4        │ 4    │ 3          │
│ Emre   │ 3    │ 5        │ 4    │ 3          │
│ Fatma  │ 3    │ 6        │ 4    │ 3          │
│ Gokhan │ 2    │ 7        │ 7    │ 4          │
│ Hale   │ 1    │ 8        │ 8    │ 5          │
└────────┴──────┴──────────┴──────┴────────────┘

Dört ödünç alan iki üye var. RANK ikisine de 2 verdi ve sonraki üyeye 3 değil 4 verdi: üçüncülük tüketilmiş sayıldı. DENSE_RANK ise ikisine 2 verip sonrakine 3 verdi; sıra numaraları boşluksuz ilerledi. ROW_NUMBER beraberliği hiç görmedi ve 2 ile 3’ü keyfî biçimde dağıttı.

Bu üç sütun üç ayrı soruya karşılık gelir. RANK, “kaç üye benden fazla aldı” sorusunun yanıtına bir eklenmiş hâlidir — yarışma sıralamalarının alışkanlığı budur. DENSE_RANK, “kaç farklı değer benden büyüktür” sorusunu yanıtlar ve seviye saymak için uygundur. ROW_NUMBER sıra değil, kimlik üretir: satırları birbirinden ayırmak için kullanılır.

Son sütunda Hale için RANK 8, DENSE_RANK 5 veriyor. İki sayı arasındaki fark beraberliklerin toplam etkisidir: sekiz üye ama beş farklı değer var.

Beraberliğin Belirsizliği

ROW_NUMBER sütununda Burak 2, Ceren 3 aldı. Bu dağılım sorgunun yazımından çıkmaz: ikisinin de adet değeri 4 olduğuna göre ORDER BY adet DESC ikisini ayırmaz. Numaraların hangisine gideceğine motor karar verdi.

Bu, kaçınılması gereken bir durumdur. Aynı sorgu farklı bir plan altında, farklı bir sürümde ya da tablo farklı sırada okunduğunda numaraları takas edebilir. ROW_NUMBER bir sonuç kümesini sayfalamak ya da tekilleştirmek için kullanılıyorsa, bu takas sessiz bir hataya dönüşür: sayfalar arasında bir kayıt iki kez görünür, bir başkası hiç görünmez.

Kural açıktır: ROW_NUMBER kullanılan pencerede ORDER BY anahtarı satırları tek biçimde belirlemelidir. Anahtar bunu sağlamıyorsa sonuna birincil anahtar gibi tekil bir sütun eklenir — ORDER BY adet DESC, ad yazımı yukarıdaki belirsizliği ortadan kaldırır. RANK ve DENSE_RANK bu sorundan etkilenmez, çünkü eş satırlara zaten aynı değeri verirler.

Dilim ve Yüzdelik Dağılım

Sıralama ailesinin diğer üyeleri, satırın sıralamadaki konumunu oransal olarak verir. NTILE(n) bölümü olabildiğince eşit n parçaya böler; PERCENT_RANK sıranın sıfır ile bir arasındaki karşılığını, CUME_DIST ise “bu satır dâhil, buraya kadar olan satırların oranını” verir:

sqlite3 -box -header <<'SQL'
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT, sube TEXT);
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT, alis TEXT, iade TEXT);
INSERT INTO uye VALUES (1,'Ayse','Kadikoy'),(2,'Burak','Kadikoy'),(3,'Ceren','Uskudar'),
  (4,'Deniz','Uskudar'),(5,'Emre','Besiktas'),(6,'Fatma','Besiktas'),
  (7,'Gokhan','Kadikoy'),(8,'Hale','Uskudar');
INSERT INTO odunc VALUES
  (1,1,1,'2024-03-01','2024-03-15'),(2,3,1,'2024-03-01','2024-03-20'),
  (3,5,1,'2024-03-04','2024-03-18'),(4,7,1,'2024-03-11',NULL),
  (5,9,1,'2024-03-18','2024-03-29'),(6,2,2,'2024-03-01','2024-03-12'),
  (7,4,2,'2024-03-06','2024-03-25'),(8,6,2,'2024-03-11','2024-03-19'),
  (9,8,2,'2024-03-21',NULL),(10,1,3,'2024-03-04','2024-03-10'),
  (11,3,3,'2024-03-06','2024-03-27'),(12,10,3,'2024-03-13','2024-03-22'),
  (13,2,3,'2024-03-25',NULL),(14,5,4,'2024-03-04','2024-03-09'),
  (15,7,4,'2024-03-13','2024-03-26'),(16,4,4,'2024-03-20',NULL),
  (17,6,5,'2024-03-06','2024-03-14'),(18,9,5,'2024-03-11','2024-03-23'),
  (19,1,5,'2024-03-25','2024-03-28'),(20,8,6,'2024-03-04','2024-03-17'),
  (21,10,6,'2024-03-13','2024-03-21'),(22,3,6,'2024-03-20',NULL),
  (23,2,7,'2024-03-11','2024-03-16'),(24,5,7,'2024-03-18','2024-03-24'),
  (25,4,8,'2024-03-13','2024-03-24');

WITH sayim AS (
  SELECT u.ad, COUNT(o.id) AS adet
  FROM uye u LEFT JOIN odunc o ON o.uye_id = u.id
  GROUP BY u.id, u.ad
)
SELECT ad, adet,
       NTILE(4) OVER s AS dilim,
       ROUND(PERCENT_RANK() OVER s, 2) AS yuzdelik_sira,
       ROUND(CUME_DIST()    OVER s, 2) AS birikimli_oran
FROM sayim
WINDOW s AS (ORDER BY adet DESC)
ORDER BY adet DESC, ad;
SQL
┌────────┬──────┬───────┬───────────────┬────────────────┐
│   ad   │ adet │ dilim │ yuzdelik_sira │ birikimli_oran │
├────────┼──────┼───────┼───────────────┼────────────────┤
│ Ayse   │ 5    │ 1     │ 0.0           │ 0.13           │
│ Burak  │ 4    │ 1     │ 0.14          │ 0.38           │
│ Ceren  │ 4    │ 2     │ 0.14          │ 0.38           │
│ Deniz  │ 3    │ 2     │ 0.43          │ 0.75           │
│ Emre   │ 3    │ 3     │ 0.43          │ 0.75           │
│ Fatma  │ 3    │ 3     │ 0.43          │ 0.75           │
│ Gokhan │ 2    │ 4     │ 0.86          │ 0.88           │
│ Hale   │ 1    │ 4     │ 1.0           │ 1.0            │
└────────┴──────┴───────┴───────────────┴────────────────┘

Dikkat çeken satır Burak ile Ceren: aynı adet değerine sahipler, yüzdelik sıraları da birikimli oranları da aynı, ama farklı dilimlere düştüler. NTILE eşleri gözetmez; satırları sayıya göre böler ve bölüm sınırı bir eş kümesinin ortasına düşerse onları ayırır. Bu yüzden dilim numarası, eşit değerli satırlar için ROW_NUMBER kadar belirsizdir.

PERCENT_RANK ilk satıra her zaman 0, CUME_DIST son satıra her zaman 1 verir. İkisi arasındaki fark tanımlarındadır: yüzdelik sıra “önümde kaç satır var” oranını, birikimli oran “benim ve benden büyük olmayanların” oranını ölçer.

Bölüm İçinde İlk N

Sıralama işlevlerinin en yaygın kullanımı, her bölümün en iyilerini seçmektir. Önceki derste belirtildiği gibi pencere işlevinin sonucu aynı sorgunun WHERE yan tümcesinde kullanılamaz; sıralama bir ortak tablo ifadesinde yapılır, süzme dışarıda:

sqlite3 -box -header <<'SQL'
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT, sube TEXT);
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT, alis TEXT, iade TEXT);
INSERT INTO uye VALUES (1,'Ayse','Kadikoy'),(2,'Burak','Kadikoy'),(3,'Ceren','Uskudar'),
  (4,'Deniz','Uskudar'),(5,'Emre','Besiktas'),(6,'Fatma','Besiktas'),
  (7,'Gokhan','Kadikoy'),(8,'Hale','Uskudar');
INSERT INTO odunc VALUES
  (1,1,1,'2024-03-01','2024-03-15'),(2,3,1,'2024-03-01','2024-03-20'),
  (3,5,1,'2024-03-04','2024-03-18'),(4,7,1,'2024-03-11',NULL),
  (5,9,1,'2024-03-18','2024-03-29'),(6,2,2,'2024-03-01','2024-03-12'),
  (7,4,2,'2024-03-06','2024-03-25'),(8,6,2,'2024-03-11','2024-03-19'),
  (9,8,2,'2024-03-21',NULL),(10,1,3,'2024-03-04','2024-03-10'),
  (11,3,3,'2024-03-06','2024-03-27'),(12,10,3,'2024-03-13','2024-03-22'),
  (13,2,3,'2024-03-25',NULL),(14,5,4,'2024-03-04','2024-03-09'),
  (15,7,4,'2024-03-13','2024-03-26'),(16,4,4,'2024-03-20',NULL),
  (17,6,5,'2024-03-06','2024-03-14'),(18,9,5,'2024-03-11','2024-03-23'),
  (19,1,5,'2024-03-25','2024-03-28'),(20,8,6,'2024-03-04','2024-03-17'),
  (21,10,6,'2024-03-13','2024-03-21'),(22,3,6,'2024-03-20',NULL),
  (23,2,7,'2024-03-11','2024-03-16'),(24,5,7,'2024-03-18','2024-03-24'),
  (25,4,8,'2024-03-13','2024-03-24');

WITH sayim AS (
  SELECT u.ad, u.sube, COUNT(o.id) AS adet
  FROM uye u LEFT JOIN odunc o ON o.uye_id = u.id
  GROUP BY u.id, u.ad, u.sube
),
siralanmis AS (
  SELECT sube, ad, adet,
         RANK()       OVER b AS sira,
         ROW_NUMBER() OVER b AS satir_no
  FROM sayim
  WINDOW b AS (PARTITION BY sube ORDER BY adet DESC)
)
SELECT sube, ad, adet, sira, satir_no FROM siralanmis
WHERE sira <= 1 ORDER BY sube, ad;
SQL
┌──────────┬───────┬──────┬──────┬──────────┐
│   sube   │  ad   │ adet │ sira │ satir_no │
├──────────┼───────┼──────┼──────┼──────────┤
│ Besiktas │ Emre  │ 3    │ 1    │ 1        │
│ Besiktas │ Fatma │ 3    │ 1    │ 2        │
│ Kadikoy  │ Ayse  │ 5    │ 1    │ 1        │
│ Uskudar  │ Ceren │ 4    │ 1    │ 1        │
└──────────┴───────┴──────┴──────┴──────────┘

Beşiktaş’tan iki satır geldi, diğerlerinden birer satır. Bunun nedeni işlev seçimidir: RANK ile süzülünce beraberlikteki tüm satırlar gelir; WHERE satir_no <= 1 yazılsaydı Fatma düşerdi ve hangisinin düştüğü belirsiz olurdu.

Seçim, sorunun ne istediğine bağlıdır. “Her şubenin en çok ödünç alan üyesi” sorusu beraberlikte iki ad istiyorsa RANK, tam olarak bir satır istiyorsa ROW_NUMBER uygundur — ikinci durumda beraberliği bozan ek bir anahtar da belirtilmelidir. WHERE sira <= 3 biçimindeki eşiği değiştirmek, aynı sorguyu ilk üçe genişletir.

Satır Numarasıyla Tekilleştirme

ROW_NUMBER bir sıralama değil ayıklama aracı olarak da kullanılır. Bölüm içinde numaralanan satırlardan yalnız birincisini almak, “her grubun bir temsilcisi” desenidir:

sqlite3 -box -header <<'SQL'
CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT, sube TEXT);
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT, alis TEXT, iade TEXT);
INSERT INTO uye VALUES (1,'Ayse','Kadikoy'),(2,'Burak','Kadikoy'),(3,'Ceren','Uskudar'),
  (4,'Deniz','Uskudar'),(5,'Emre','Besiktas'),(6,'Fatma','Besiktas'),
  (7,'Gokhan','Kadikoy'),(8,'Hale','Uskudar');
INSERT INTO odunc VALUES
  (1,1,1,'2024-03-01','2024-03-15'),(2,3,1,'2024-03-01','2024-03-20'),
  (3,5,1,'2024-03-04','2024-03-18'),(4,7,1,'2024-03-11',NULL),
  (5,9,1,'2024-03-18','2024-03-29'),(6,2,2,'2024-03-01','2024-03-12'),
  (7,4,2,'2024-03-06','2024-03-25'),(8,6,2,'2024-03-11','2024-03-19'),
  (9,8,2,'2024-03-21',NULL),(10,1,3,'2024-03-04','2024-03-10'),
  (11,3,3,'2024-03-06','2024-03-27'),(12,10,3,'2024-03-13','2024-03-22'),
  (13,2,3,'2024-03-25',NULL),(14,5,4,'2024-03-04','2024-03-09'),
  (15,7,4,'2024-03-13','2024-03-26'),(16,4,4,'2024-03-20',NULL),
  (17,6,5,'2024-03-06','2024-03-14'),(18,9,5,'2024-03-11','2024-03-23'),
  (19,1,5,'2024-03-25','2024-03-28'),(20,8,6,'2024-03-04','2024-03-17'),
  (21,10,6,'2024-03-13','2024-03-21'),(22,3,6,'2024-03-20',NULL),
  (23,2,7,'2024-03-11','2024-03-16'),(24,5,7,'2024-03-18','2024-03-24'),
  (25,4,8,'2024-03-13','2024-03-24');

WITH numarali AS (
  SELECT id, uye_id, alis,
         ROW_NUMBER() OVER (PARTITION BY uye_id ORDER BY alis DESC, id DESC) AS no
  FROM odunc
)
SELECT n.uye_id, u.ad, n.id AS odunc_id, n.alis
FROM numarali n JOIN uye u ON u.id = n.uye_id
WHERE n.no = 1 ORDER BY n.uye_id;
SQL
┌────────┬────────┬──────────┬────────────┐
│ uye_id │   ad   │ odunc_id │    alis    │
├────────┼────────┼──────────┼────────────┤
│ 1      │ Ayse   │ 5        │ 2024-03-18 │
│ 2      │ Burak  │ 9        │ 2024-03-21 │
│ 3      │ Ceren  │ 13       │ 2024-03-25 │
│ 4      │ Deniz  │ 16       │ 2024-03-20 │
│ 5      │ Emre   │ 19       │ 2024-03-25 │
│ 6      │ Fatma  │ 22       │ 2024-03-20 │
│ 7      │ Gokhan │ 24       │ 2024-03-18 │
│ 8      │ Hale   │ 25       │ 2024-03-13 │
└────────┴────────┴──────────┴────────────┘

Sıralama anahtarı alis DESC, id DESC biçiminde iki sütunludur. alis tek başına aynı üye içinde yinelenebilir; id birincil anahtar olduğu için eklenmesi sonucu belirlenimci kılar. Bu, önceki bölümdeki kuralın uygulanmış hâlidir.

Aynı sonuç, İlişkili Alt Sorgular dersindeki MAX alt sorgusuyla da üretilebilirdi. İki yazımın farkı, beraberlik durumunda ortaya çıkar: alt sorgulu yazım aynı tarihe sahip iki kaydın ikisini de döndürür, satır numarasıyla yazım tam olarak bir tanesini seçer.

Özet

  • ROW_NUMBER eşlik tanımaz, RANK eşlere aynı sırayı verip atlar, DENSE_RANK eşlere aynı sırayı verip atlamaz.
  • RANK “kaç satır benden önde”, DENSE_RANK “kaç farklı değer benden önde” sorusunu yanıtlar; ROW_NUMBER sıra değil kimlik üretir.
  • ROW_NUMBER ve NTILE sonuçları, sıralama anahtarı satırları tek biçimde belirlemiyorsa belirlenimci değildir; anahtara tekil bir sütun eklenmelidir.
  • Pencere sonucuna göre süzmek için sorgu bir ortak tablo ifadesine sarılır; bölüm içinde ilk N seçiminde RANK beraberlikteki tüm satırları, ROW_NUMBER tam olarak N satırı getirir.
  • PERCENT_RANK ilk satıra 0, CUME_DIST son satıra 1 verir; NTILE eş satırları farklı dilimlere ayırabilir.

Sonraki Adım

Buraya kadarki her sorgunun sonucu, sütunları sorgu yazılırken belli olan bir tabloydu. Raporlarda ise sık sık tersi istenir: satırlardaki değerler sütun başlığına çıksın, her tür ya da her ay ayrı bir sütun olsun. Bu dönüşüm, satır sayısını azaltıp sütun sayısını artırır ve SQL’in sabit sütun listesi kuralıyla doğrudan çelişir. Sonraki ders, döndürme işlemlerini koşullu toplamayla nasıl yazıldığını ve ters yönde satıra dönüştürmenin nasıl yapıldığını gösterecek.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat