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_NUMBEReşlik tanımaz,RANKeşlere aynı sırayı verip atlar,DENSE_RANKeşlere aynı sırayı verip atlamaz.RANK“kaç satır benden önde”,DENSE_RANK“kaç farklı değer benden önde” sorusunu yanıtlar;ROW_NUMBERsıra değil kimlik üretir.ROW_NUMBERveNTILEsonuç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
RANKberaberlikteki tüm satırları,ROW_NUMBERtam olarak N satırı getirir. PERCENT_RANKilk satıra 0,CUME_DISTson satıra 1 verir;NTILEeş 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.