Columns of blocks with the top blocks raised, representing top N rows per group in SQL Server

SQL Server’da Grup Başına İlk N Kayıt: ROW_NUMBER, DENSE_RANK ve CROSS APPLY

SQL Server’da grup başına ilk N kaydı almanın üç yolu, ROW_NUMBER, RANK ve DENSE_RANK’in eşitlikleri nasıl ele aldığı ve hangi index ile CROSS APPLY ve TOP’un en hızlı seçenek olduğu.

Temmuz 9, 2023

“Her departmanın en yüksek maaşlı üç çalışanı”, “her müşterinin son beş siparişi”, “her ürün grubunun en çok satan on ürünü”… Bunlar aynı sorunun farklı biçimleridir: grup başına ilk N kayıt. T-SQL’de bunu çözmenin birden fazla yolu vardır ve hepsi aynı sonucu vermez. Eşitlikler (aynı maaşa sahip iki çalışan) nasıl ele alınıyor, sorgu hangi index’i kullanıyor ve büyük tablolarda ne kadar okuma yapıyor, seçimi belirleyen sorulardır.

Bu yazıda bir örnek üzerinden üç yöntemi, eşitlik davranışlarını ve performans farklarını karşılaştırıyoruz.

SQL Server Performans Danışmanlığı

Raporlama sorgularınız yavaş mı?

Aryasoft, raporlama sorgularınızı yeniden yazar ve doğru index'lerle hem doğru hem hızlı sonuç üretir hale getirir.

Destek Alın

Örnek Veri

CREATE TABLE dbo.Departman (
    DepartmanId int PRIMARY KEY,
    DepartmanAdi nvarchar(50) NOT NULL
);

CREATE TABLE dbo.Calisan (
    CalisanId   int PRIMARY KEY,
    AdSoyad     nvarchar(100) NOT NULL,
    DepartmanId int NOT NULL REFERENCES dbo.Departman (DepartmanId),
    Maas        decimal(19, 2) NOT NULL
);

Hedef: her departmanın en yüksek maaşlı üç çalışanını, departman adıyla birlikte listelemek.

Yöntem 1: ROW_NUMBER

WITH sirali AS (
    SELECT c.CalisanId, c.AdSoyad, c.DepartmanId, c.Maas,
           ROW_NUMBER() OVER (PARTITION BY c.DepartmanId
                              ORDER BY c.Maas DESC, c.CalisanId) AS sira
    FROM dbo.Calisan AS c
)
SELECT d.DepartmanAdi, s.AdSoyad, s.Maas, s.sira
FROM sirali AS s
JOIN dbo.Departman AS d ON d.DepartmanId = s.DepartmanId
WHERE s.sira <= 3
ORDER BY d.DepartmanAdi, s.sira;

Her departmandan tam olarak üç satır döner. Aynı maaşa sahip çalışanlar varsa, ORDER BY içindeki ikinci kolon (CalisanId) hangisinin listeye gireceğini belirler. Bu ikinci kolon olmadan sonuç çalıştırmadan çalıştırmaya değişebilir.

Yöntem 2: DENSE_RANK ile Eşitlikleri Dahil Etmek

“En yüksek üç maaş seviyesindeki herkes” isteniyorsa, eşitlikleri dahil eden bir sıralama fonksiyonu kullanın:

WITH sirali AS (
    SELECT c.AdSoyad, c.DepartmanId, c.Maas,
           DENSE_RANK() OVER (PARTITION BY c.DepartmanId ORDER BY c.Maas DESC) AS sira
    FROM dbo.Calisan AS c
)
SELECT d.DepartmanAdi, s.AdSoyad, s.Maas, s.sira
FROM sirali AS s
JOIN dbo.Departman AS d ON d.DepartmanId = s.DepartmanId
WHERE s.sira <= 3
ORDER BY d.DepartmanAdi, s.sira;
Fonksiyon Eşit maaşlar Sonuç sayısı
ROW_NUMBER Farklı numaralar alır; biri dışarıda kalabilir Grup başına tam N
RANK Aynı numarayı alır, sonraki numara atlanır (1, 1, 3) N’den fazla olabilir
DENSE_RANK Aynı numarayı alır, atlama olmaz (1, 1, 2) İlk N seviyedeki herkes

Not

Hangi fonksiyonun doğru olduğu teknik değil, iş kuralı sorusudur. “En yüksek üç maaş” ile “en yüksek maaşlı üç kişi” farklı sonuçlar üretir. Raporu isteyen kişiyle eşitliklerin nasıl ele alınacağını netleştirin.

Yöntem 3: CROSS APPLY ve TOP

Departman sayısı az, departman başına çalışan sayısı çoksa, her departman için ayrı bir TOP sorgusu çalıştırmak çok daha az okumayla sonuç verebilir:

SELECT d.DepartmanAdi, t.AdSoyad, t.Maas
FROM dbo.Departman AS d
CROSS APPLY (
    SELECT TOP (3) c.AdSoyad, c.Maas
    FROM dbo.Calisan AS c
    WHERE c.DepartmanId = d.DepartmanId
    ORDER BY c.Maas DESC, c.CalisanId
) AS t
ORDER BY d.DepartmanAdi, t.Maas DESC;

Eşitlikleri dahil etmek için TOP (3) WITH TIES kullanılabilir; bu durumda ORDER BY yalnızca Maas DESC olmalıdır.

Performansın Anahtarı: Doğru Index

Üç yöntem de aynı index’ten yararlanır: gruplama kolonu önde, sıralama kolonu ikinci sırada ve döndürülen kolonlar INCLUDE ile.

CREATE NONCLUSTERED INDEX IX_Calisan_Departman_Maas
ON dbo.Calisan (DepartmanId, Maas DESC)
INCLUDE (AdSoyad);
  • ROW_NUMBER ve DENSE_RANK: Index sayesinde ayrı bir sıralama işlemine gerek kalmaz; ama yine de tablonun tüm satırları okunur ve numaralandırılır.
  • CROSS APPLY + TOP: Her departman için index’te doğrudan ilgili yere gidip yalnızca üç satır okur. Departman sayısı azken çok daha az okuma yapar; departman sayısı çok fazlaysa bu avantaj azalır.

İpucu

İki yöntemi kendi verinizde SET STATISTICS IO ON ile karşılaştırın; logical reads farkı hangi yöntemin daha uygun olduğunu açıkça gösterir. Ayrıntılar okumalar yazımızda.

Özet

  • Grup başına tam N satır: ROW_NUMBER, ORDER BY benzersiz bir kolonla bitmeli.
  • Eşitlikler dahil: DENSE_RANK ya da TOP … WITH TIES.
  • Az sayıda grup, grup başına çok satır: CROSS APPLY + TOP genellikle en az okumayı yapar.
  • Her durumda: gruplama ve sıralama kolonlarına uygun bir index.
ROW_NUMBER() OVER (PARTITION BY grup ORDER BY sıralama) ile her gruba sıra numarası verip sıra numarası N’den küçük ya da eşit olanları seçerek ya da CROSS APPLY içinde TOP (N) kullanarak.
ROW_NUMBER eşitlerde bile farklı numara verir. RANK eşitlere aynı numarayı verip sonrakini atlar. DENSE_RANK eşitlere aynı numarayı verir ve atlama yapmaz.
Eşitlikleri dahil etmek için DENSE_RANK ya da TOP … WITH TIES kullanın. Tam N satır gerekiyorsa ROW_NUMBER’ın ORDER BY’ına benzersiz bir kolon ekleyin.
Grup sayısı azken CROSS APPLY + TOP genellikle daha az okuma yapar. Her yöntem için gruplama ve sıralama kolonlarını içeren bir index gerekir.

SQL Server Performans Danışmanlığı

Raporlarınız hem doğru hem hızlı olsun

Aryasoft’un kıdemli DBA ekibi raporlama sorgularınızı iş kurallarına uygun şekilde yeniden yazar, doğru index’lerle okumaları azaltır ve sonuçları ölçerek doğrular.

4.7/5