MariaDB ve MySQL’de RANK ve DENSE_RANK Kullanım Farkları

Veri analizi yaparken sıralama fonksiyonları hayat kurtarıcı olabiliyor. Özellikle “en çok satan ürün hangisi?”, “en yüksek maaşlı çalışan kim?” veya “hangi müşteri en fazla sipariş veriyor?” gibi soruları yanıtlarken RANK ve DENSE_RANK fonksiyonlarına sıkça başvurursunuz. Ancak bu iki fonksiyon arasındaki farkı kavramadan yazdığınız sorgular sizi yanıltabilir. Bu yazıda her iki fonksiyonu detaylıca inceleyeceğiz, gerçek dünya senaryolarıyla farkları netleştireceğiz ve hangi durumda hangisini kullanmanız gerektiğini öğreneceğiz.

Pencere Fonksiyonlarına Kısa Bir Giriş

RANK ve DENSE_RANK, SQL’de pencere fonksiyonları (window functions) kategorisine girer. Pencere fonksiyonları, bir sorgu sonuç kümesi üzerinde belirli bir “pencere” tanımlayarak her satır için hesaplama yapmanızı sağlar. Aggregate fonksiyonlardan (SUM, AVG, COUNT gibi) temel farkı şudur: pencere fonksiyonları satırları gruplandırmaz, her satırı koruyarak üzerinde işlem yapar.

MariaDB 10.2 ve MySQL 8.0 sürümlerinden itibaren pencere fonksiyonları resmi olarak desteklenmektedir. Eğer daha eski sürüm kullanıyorsanız bu fonksiyonları kullanamazsınız; bunun yerine değişken tabanlı ya da alt sorgu yöntemlerine başvurmanız gerekir.

-- MariaDB / MySQL sürümünü kontrol et
SELECT VERSION();

-- Pencere fonksiyonu desteği için minimum sürümler:
-- MySQL: 8.0+
-- MariaDB: 10.2+

RANK Fonksiyonu Nasıl Çalışır?

RANK fonksiyonu, bir sonuç kümesindeki her satıra sıra numarası atar. Ancak burada kritik bir nokta var: eğer iki satırın değeri birbirine eşitse, her iki satıra da aynı sıra numarası verilir ve bir sonraki sıra numarası atlanır.

Bunu somutlaştırmak için şöyle düşünün: Bir koşu yarışmasında iki sporcu aynı anda birinciliği paylaşıyorsa, her ikisine de 1. sıra verilir ve 2. sıra yoktur, doğrudan 3. sıraya geçilir.

Örnek veri setiyle görelim:

-- Örnek tablo oluştur
CREATE TABLE satis_temsilcileri (
    id INT AUTO_INCREMENT PRIMARY KEY,
    ad VARCHAR(100),
    departman VARCHAR(50),
    aylik_satis DECIMAL(10,2)
);

INSERT INTO satis_temsilcileri (ad, departman, aylik_satis) VALUES
('Ahmet Yılmaz', 'Kuzey', 15000.00),
('Mehmet Kaya', 'Güney', 22000.00),
('Ayşe Demir', 'Kuzey', 22000.00),
('Fatma Çelik', 'Güney', 18000.00),
('Ali Şahin', 'Kuzey', 30000.00),
('Zeynep Arslan', 'Güney', 18000.00),
('Mustafa Koç', 'Kuzey', 25000.00),
('Elif Öztürk', 'Güney', 30000.00);
-- RANK fonksiyonu ile sıralama
SELECT
    ad,
    departman,
    aylik_satis,
    RANK() OVER (ORDER BY aylik_satis DESC) AS sira
FROM satis_temsilcileri;

Bu sorgunun çıktısına bakacak olursak:

  • Ali Şahin – 30000 – Sıra: 1
  • Elif Öztürk – 30000 – Sıra: 1
  • Mustafa Koç – 25000 – Sıra: 3
  • Mehmet Kaya – 22000 – Sıra: 4
  • Ayşe Demir – 22000 – Sıra: 4
  • Fatma Çelik – 18000 – Sıra: 6
  • Zeynep Arslan – 18000 – Sıra: 6
  • Ahmet Yılmaz – 15000 – Sıra: 8

Dikkat edin: 1. sırada iki kişi var ve 2. sıra yok. Doğrudan 3. sıraya atlandı. Benzer şekilde 4. sırada iki kişi var ve 5. sıra yok, 6. sıraya atlandı.

DENSE_RANK Fonksiyonu Nasıl Çalışır?

DENSE_RANK de eşit değerlere aynı sıra numarasını verir. Ancak RANK’tan farklı olarak sıra numaraları arasında boşluk bırakmaz. “Dense” kelimesinin anlamı olan “yoğun/sıkışık” bunu güzel özetliyor.

-- DENSE_RANK fonksiyonu ile sıralama
SELECT
    ad,
    departman,
    aylik_satis,
    DENSE_RANK() OVER (ORDER BY aylik_satis DESC) AS yogun_sira
FROM satis_temsilcileri;

Bu sorgunun çıktısı:

  • Ali Şahin – 30000 – Sıra: 1
  • Elif Öztürk – 30000 – Sıra: 1
  • Mustafa Koç – 25000 – Sıra: 2
  • Mehmet Kaya – 22000 – Sıra: 3
  • Ayşe Demir – 22000 – Sıra: 3
  • Fatma Çelik – 18000 – Sıra: 4
  • Zeynep Arslan – 18000 – Sıra: 4
  • Ahmet Yılmaz – 15000 – Sıra: 5

Görüyorsunuz: 1’den sonra 2 geliyor, 3’ten sonra 4 geliyor. Hiçbir sıra numarası atlanmıyor.

İkisini Yan Yana Görmek

Farkı daha net görmek için her iki fonksiyonu aynı sorguda kullanalım:

-- RANK ve DENSE_RANK karşılaştırması
SELECT
    ad,
    aylik_satis,
    RANK() OVER (ORDER BY aylik_satis DESC) AS rank_sira,
    DENSE_RANK() OVER (ORDER BY aylik_satis DESC) AS dense_rank_sira,
    ROW_NUMBER() OVER (ORDER BY aylik_satis DESC) AS satir_no
FROM satis_temsilcileri
ORDER BY aylik_satis DESC;

ROW_NUMBER’ı da ekledim çünkü üçlüyü bir arada görmek kavramı çok netleştiriyor:

  • ROW_NUMBER: Her satıra benzersiz numara verir, eşitliklerde bile tekrar etmez
  • RANK: Eşitliklerde aynı numarayı verir, sonrasında boşluk bırakır
  • DENSE_RANK: Eşitliklerde aynı numarayı verir, sonrasında boşluk bırakmaz

PARTITION BY ile Gruba Özel Sıralama

Gerçek hayatta çoğu zaman tüm veri seti üzerinde değil, belirli gruplar içinde sıralama yapmak istersiniz. Departman bazında en iyi satış temsilcisini bulmak gibi. İşte burada PARTITION BY devreye giriyor.

-- Departman bazında RANK kullanımı
SELECT
    ad,
    departman,
    aylik_satis,
    RANK() OVER (
        PARTITION BY departman
        ORDER BY aylik_satis DESC
    ) AS departman_sirasi,
    DENSE_RANK() OVER (
        PARTITION BY departman
        ORDER BY aylik_satis DESC
    ) AS departman_yogun_sirasi
FROM satis_temsilcileri
ORDER BY departman, aylik_satis DESC;

Bu sorgu çalıştığında her departman kendi içinde sıfırdan sıralanıyor. Kuzey departmanı için 1, 2, 3… Güney departmanı için tekrar 1, 2, 3… şeklinde.

PARTITION BY olmadan sıralama tüm sonuç kümesi üzerinde yapılır. PARTITION BY ile her grup içinde ayrı ayrı yapılır.

Gerçek Dünya Senaryosu 1: E-ticaret Ürün Sıralaması

Bir e-ticaret platformunda her kategorideki en çok satan ilk 3 ürünü listelemek istiyorsunuz. DENSE_RANK burada daha mantıklı çünkü kullanıcıya “3. en popüler ürün” dediğinizde sıra atlamamış olmanız gerekiyor.

-- Ürün satış tablosu
CREATE TABLE urun_satislari (
    urun_id INT,
    urun_adi VARCHAR(200),
    kategori VARCHAR(100),
    toplam_satis INT
);

INSERT INTO urun_satislari VALUES
(1, 'Laptop A', 'Elektronik', 450),
(2, 'Laptop B', 'Elektronik', 320),
(3, 'Telefon X', 'Elektronik', 450),
(4, 'Telefon Y', 'Elektronik', 280),
(5, 'Tablet Z', 'Elektronik', 190),
(6, 'Tişört Kırmızı', 'Giyim', 800),
(7, 'Tişört Mavi', 'Giyim', 800),
(8, 'Pantolon Siyah', 'Giyim', 600),
(9, 'Ceket Kahve', 'Giyim', 400);

-- Her kategoride ilk 3 ürünü getir (DENSE_RANK ile)
SELECT *
FROM (
    SELECT
        urun_adi,
        kategori,
        toplam_satis,
        DENSE_RANK() OVER (
            PARTITION BY kategori
            ORDER BY toplam_satis DESC
        ) AS kategori_sirasi
    FROM urun_satislari
) ranked
WHERE kategori_sirasi <= 3
ORDER BY kategori, kategori_sirasi;

Bu sorguda RANK kullansaydınız ne olurdu? Elektronik kategorisinde Laptop A ve Telefon X her ikisi de 1. sırada olurdu, 2. sıra atlanırdı ve Laptop B 3. sıradan 4. sıraya düşerdi. Sonuç olarak “ilk 3” filtrenizde 4 ürün görürsünüz çünkü 3 numaralı RANK yoktur. DENSE_RANK ile bu problem ortadan kalkar.

Gerçek Dünya Senaryosu 2: Maaş Analizi ve Bordro Yönetimi

İK departmanları maaş bantlarını analiz ederken DENSE_RANK çok işe yarar. “Kaçıncı maaş seviyesindedir?” sorusuna cevap ararken boşluklu bir sıralama kafa karıştırır.

-- Çalışan maaş analizi
CREATE TABLE calisanlar (
    id INT AUTO_INCREMENT PRIMARY KEY,
    ad VARCHAR(100),
    unvan VARCHAR(100),
    bolum VARCHAR(50),
    maas DECIMAL(10,2)
);

INSERT INTO calisanlar (ad, unvan, bolum, maas) VALUES
('Can Yıldız', 'Kıdemli Geliştirici', 'Teknoloji', 18000),
('Selin Ak', 'Geliştirici', 'Teknoloji', 12000),
('Burak Şen', 'Kıdemli Geliştirici', 'Teknoloji', 18000),
('Deniz Kara', 'Proje Müdürü', 'Teknoloji', 22000),
('Gül Yıldırım', 'Geliştirici', 'Teknoloji', 14000),
('Mert Aydın', 'Sistem Uzmanı', 'Altyapı', 16000),
('Pınar Doğan', 'Ağ Uzmanı', 'Altyapı', 16000),
('Emre Kılıç', 'Altyapı Müdürü', 'Altyapı', 24000);

-- Bölüm içi maaş sıralaması ve seviye analizi
SELECT
    ad,
    unvan,
    bolum,
    maas,
    RANK() OVER (
        PARTITION BY bolum
        ORDER BY maas DESC
    ) AS rank_sirasi,
    DENSE_RANK() OVER (
        PARTITION BY bolum
        ORDER BY maas DESC
    ) AS maas_seviyesi,
    COUNT(*) OVER (PARTITION BY bolum) AS bolum_kisi_sayisi,
    ROUND(
        maas * 100.0 / SUM(maas) OVER (PARTITION BY bolum),
        2
    ) AS bolum_maas_yuzdesi
FROM calisanlar
ORDER BY bolum, maas DESC;

Bu sorguda DENSE_RANK’ın döndürdüğü maas_seviyesi kolonu size “Teknoloji bölümünde 2. maaş seviyesi” gibi temiz bir bilgi veriyor. RANK kullansaydınız 22000 maaşlı biri 1. sırada olurken 18000 maaşlılar 2. sırada olmak yerine 3. sırada olurdu ve “2. maaş seviyesi yok” gibi garip bir durum ortaya çıkardı.

RANK’ın Doğru Kullanım Alanı: Yarışma ve Spor Verileri

RANK’ın mantıklı olduğu durumlar da var. Spor müsabakaları bunun en güzel örneği. Gerçek hayatta da iki sporcu birinci olduğunda ikinci sıra gerçekten boş kalır ve üçüncü sıra verilir. Bu durumda RANK davranışı beklentiyle örtüşür.

-- Atletizm yarışması sonuçları
CREATE TABLE yaris_sonuclari (
    yarismaci_id INT,
    yarismaci_adi VARCHAR(100),
    sure_saniye DECIMAL(8,3),
    yaris_tarihi DATE
);

INSERT INTO yaris_sonuclari VALUES
(1, 'Hasan Güler', 10.234, '2024-03-15'),
(2, 'İbrahim Yurt', 10.456, '2024-03-15'),
(3, 'Sercan Başer', 10.234, '2024-03-15'),
(4, 'Tolga Ersoy', 10.789, '2024-03-15'),
(5, 'Uğur Menteş', 11.001, '2024-03-15');

-- Yarışma sıralaması (RANK kullanımı mantıklı)
SELECT
    yarismaci_adi,
    sure_saniye,
    RANK() OVER (ORDER BY sure_saniye ASC) AS resmi_sira,
    DENSE_RANK() OVER (ORDER BY sure_saniye ASC) AS dense_sira
FROM yaris_sonuclari
ORDER BY sure_saniye;

Burada resmi_sira sütununda 1, 1, 3, 4, 5 görürsünüz. İki kişi birden birinci olmuştur ve resmi olarak 2. sıra yoktur. Bu gerçek hayatla örtüşür. Eğer DENSE_RANK kullansaydınız 1, 1, 2, 3, 4 görecektiniz ki bu durumda sanki 2. sıra doluymuş gibi yanılgı yaratırdı.

Performans Notları ve İndeks Kullanımı

Pencere fonksiyonları veritabanı motorunu zorlar. Özellikle büyük tablolarda RANK veya DENSE_RANK kullandığınızda sorgu planını mutlaka incelemelisiniz.

-- Sorgu planını analiz et
EXPLAIN SELECT
    ad,
    aylik_satis,
    RANK() OVER (ORDER BY aylik_satis DESC) AS sira
FROM satis_temsilcileri;

-- ORDER BY ve PARTITION BY kolonlarına indeks eklemek performansı artırır
CREATE INDEX idx_satis_departman_satis
ON satis_temsilcileri (departman, aylik_satis DESC);

-- İndeks sonrası tekrar analiz et
EXPLAIN SELECT
    ad,
    departman,
    aylik_satis,
    RANK() OVER (
        PARTITION BY departman
        ORDER BY aylik_satis DESC
    ) AS sira
FROM satis_temsilcileri;

Dikkat etmeniz gereken noktalar:

  • PARTITION BY kolonu: Mutlaka indekslenmiş olmalı
  • ORDER BY kolonu: Mümkünse bileşik indeks içinde olmalı
  • Büyük tablolar: Alt sorgu ile önce filtrele, sonra sırala
  • Geçici tablolar: Çok karmaşık pencere sorgularında ara sonuçları geçici tabloya almak mantıklı olabilir

Alt Sorgu ile Filtreleme Tekniği

Sıralama fonksiyonları WHERE koşulunda doğrudan kullanılamaz. Bunu sysadmin’ler arasında yaygın bir hata olarak sıkça görüyorum. Alt sorgu veya CTE kullanmak zorundasınız.

-- YANLIŞ kullanım (hata verir)
-- SELECT ad, aylik_satis, RANK() OVER (...) AS sira
-- FROM satis_temsilcileri
-- WHERE RANK() OVER (...) <= 3;  -- Bu çalışmaz!

-- DOĞRU kullanım 1: Alt sorgu
SELECT ad, departman, aylik_satis, sira
FROM (
    SELECT
        ad,
        departman,
        aylik_satis,
        DENSE_RANK() OVER (
            PARTITION BY departman
            ORDER BY aylik_satis DESC
        ) AS sira
    FROM satis_temsilcileri
) alt_sorgu
WHERE sira <= 2;

-- DOĞRU kullanım 2: CTE (Common Table Expression) ile
WITH sirali_calisanlar AS (
    SELECT
        ad,
        departman,
        aylik_satis,
        DENSE_RANK() OVER (
            PARTITION BY departman
            ORDER BY aylik_satis DESC
        ) AS sira
    FROM satis_temsilcileri
)
SELECT *
FROM sirali_calisanlar
WHERE sira = 1;

CTE yaklaşımı özellikle karmaşık sorgularda okunabilirliği artırır ve hata ayıklamayı kolaylaştırır.

Hangi Fonksiyonu Ne Zaman Kullanmalı?

Özetle karar vermek için şu soruları kendinize sorun:

RANK kullanın:

  • Gerçek hayat yarışma ve müsabaka sonuçları için (resmi sıralama mantığı)
  • “Kaçıncı” yerine “sırasına kadar olan toplam aday sayısı” gibi analizler
  • Sıra atlamasının anlamlı olduğu iş kuralları için

DENSE_RANK kullanın:

  • “İlk N” filtrelemesi yapacaksanız (WHERE sira <= 3 gibi)
  • Maaş seviyeleri, fiyat kademeleri gibi kategorik sıralama
  • Kullanıcıya gösterilecek sıra numaraları (2. ürün gerçekten 2. olmalı)
  • Sıra numarası boşluğunun mantıksal anlam taşımadığı durumlar

ROW_NUMBER kullanın:

  • Benzersiz sıra numarası zorunluysa (sayfalama, pivot tablolar)
  • Eşit değerlerde bile farklı numara istiyorsanız
  • Tekil kayıt seçimi için (birden fazla eşit değerden birini almak)

Sonuç

RANK ve DENSE_RANK arasındaki fark tek cümlede şudur: ikisi de eşit değerlere aynı sıra numarasını verir, ancak RANK sonrasında boşluk bırakırken DENSE_RANK bırakmaz. Bu küçük fark, yanlış tercihte bulunduğunuzda raporlarınızda tutarsız sonuçlara veya kayıp verilere yol açabilir.

Özellikle “ilk N kaydı getir” senaryolarında DENSE_RANK kullanmak çok daha güvenlidir. Spor müsabakaları gibi gerçek hayat sıralama mantığını yansıtmak istediğinizde ise RANK daha doğru seçimdir. Her iki fonksiyonu da PARTITION BY ile birleştirerek grup bazlı analizlerde güçlü sonuçlar elde edebilirsiniz.

Son bir pratik öneri olarak: yeni bir sıralama sorgusu yazmadan önce mutlaka her iki fonksiyonla test edin ve beklediğiniz çıktıyı hangisinin verdiğini doğrulayın. Özellikle eşit değerlerin yoğun olduğu veri setlerinde davranış farkı çok belirgin şekilde ortaya çıkar.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir