Tüm yazılarPerSight Ekibi · Son güncelleme: 23 Temmuz 2026 · 8 dk okuma

Her Grupta En Yüksek N Kayıt: SQL'de Top-N per Group

Her kategoride en pahalı 3 ürün, her müşterinin son siparişi: grup başına en iyi N kaydı ROW_NUMBER ile bulmak. Pencere fonksiyonu kalıbı ve MySQL 5.7 için alternatif.

"Her kategoride en pahalı üç ürün", "her müşterinin en son siparişi", "her şubede en çok satan ürün". Hepsi aynı kalıbın bir çeşitlemesidir: bir gruba göre böl, her grubun içinde sırala, en tepedeki N tanesini al. Adı "top-N per group" ve düz bir ORDER BY ... LIMIT ile çözülmez, çünkü LIMIT tüm sonuçtan keser, her gruptan değil.

Neden basit LIMIT yetmiyor?

ORDER BY fiyat DESC LIMIT 3 size tüm tablodaki en pahalı üç ürünü verir; hepsi tek bir kategoriden olabilir. Siz ise her kategori için ayrı ayrı ilk üçü istiyorsunuz. İşte pencere fonksiyonları tam da bunun için var.

ROW_NUMBER ile kalıp nasıl kurulur?

ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) her grup içinde 1'den başlayan bir sıra numarası üretir. Sonra bu numarayı dışarıdan filtrelersiniz. Her kategoride en pahalı üç ürün:

SELECT kategori, ad, fiyat
FROM (
  SELECT kategori, ad, fiyat,
         ROW_NUMBER() OVER (
           PARTITION BY kategori
           ORDER BY fiyat DESC
         ) AS sira
  FROM urunler
) t
WHERE sira <= 3
ORDER BY kategori, sira;
PostgreSQL, SQL Server, Oracle ve MySQL 8+ için aynı sözdizimi.

PARTITION BY kategori "her kategori kendi içinde" demektir; ORDER BY fiyat DESC en pahalıya 1 numarayı verir. İç sorguda numarayı üretir, dış sorguda sira <= 3 ile ilk üçü süzersiniz. Numarayı doğrudan WHERE'de kullanamazsınız; bu yüzden iki katman gerekir.

ROW_NUMBER, RANK, DENSE_RANK: hangisi?

Üçü de sıralar ama beraberliklerde farklı davranır. ROW_NUMBER eşit değerlere bile farklı numara verir (kesinlikle N satır istersiniz). RANK eşitlere aynı numarayı verip sonrasını atlar (1,1,3). DENSE_RANK atlama yapmaz (1,1,2). "En pahalı 3 fiyat" (beraberlikler dahil olsun) istiyorsanız DENSE_RANK; "tam 3 satır" istiyorsanız ROW_NUMBER doğru seçimdir.

MySQL 5.7 gibi pencere fonksiyonu olmayan yerlerde ne yaparım?

Pencere fonksiyonları MySQL'e 8.0 ile geldi. Daha eski bir sürümde kaldıysanız, ilişkili (correlated) bir alt sorguyla "bu satırdan daha pahalı, aynı kategoride kaç ürün var?" diye sayar ve bu sayı N'den küçük olanları alırsınız:

SELECT u.kategori, u.ad, u.fiyat
FROM urunler u
WHERE (
  SELECT COUNT(*)
  FROM urunler x
  WHERE x.kategori = u.kategori
    AND x.fiyat > u.fiyat
) < 3
ORDER BY u.kategori, u.fiyat DESC;
Pencere fonksiyonu olmayan sürümler için taşınabilir alternatif.

Bu yöntem küçük tablolarda gayet iyi çalışır; büyük tablolarda pencere fonksiyonu hem daha hızlı hem daha okunurdur. Bir ayrıntı: eşit değerler aynı sayımı paylaştığı için beraberlik olduğunda N'den fazla satır dönebilir; kesin N satır gerekiyorsa pencere fonksiyonlu kalıp şart. Mümkünse veritabanını güncelleyip ilk kalıba geçin.

Sık sorulan sorular

Her gruptan yalnızca en yüksek 1 kaydı istiyorum; daha kısa bir yol var mı?
PostgreSQL'de DISTINCT ON (grup_kolonu) ... ORDER BY grup_kolonu, sıralama_kolonu çok temizdir. Taşınabilir yol yine ROW_NUMBER ile sira = 1 filtresidir; her veritabanında çalışır.
PARTITION BY olmadan ORDER BY yazarsam ne olur?
PARTITION BY vermezseniz tüm satırlar tek bir grup sayılır ve numaralandırma baştan sona tek akış olur; yani "grup başına" değil, "tablo geneli" top-N elde edersiniz. Grup istiyorsanız PARTITION BY şarttır.
Sıra numarasını neden doğrudan WHERE'de kullanamıyorum?
Çünkü WHERE, pencere fonksiyonu hesaplanmadan önce çalışır. Bu yüzden numarayı bir iç sorguda (ya da CTE'de) üretip dıştaki WHERE ile filtrelemek gerekir.
Beraberlik olduğunda tam N satır garantisi nasıl olur?
ROW_NUMBER kullanın; eşit değerlere bile ayrı numara verdiği için her zaman tam N satır döner. Hangi eşitin önce geleceğini belirlemek isterseniz ORDER BY'a ikinci bir kolon (örneğin id) ekleyin.

Bu iki katmanlı kalıbı ezberlemek yerine PerSight'a "her kategoride en pahalı 3 ürünü göster" ya da "her müşterinin son siparişini getir" diyebilirsiniz. Doğru pencere fonksiyonunu kendisi seçer, kurduğu sorguyu da gösterir.

Kaynaklar