Her kategori için en iyi 10 kaydı seçin


208

Bir sorguda her bölümden ilk 10 kayıt döndürmek istiyorum. Nasıl yapılacağını bilen var mı? Bölüm, tablodaki sütunlardan biridir.

Veritabanı SQL Server 2005'tir. Girilen tarihe göre ilk 10'a dönmek istiyorum. Bölümler iş, yerel ve özelliktir. Belirli bir tarih için yalnızca üst (10) iş satırı (en son giriş), üst (10) yerel satır ve üst (10) özellikleri istiyorum.


Bu cevaplardan herhangi biri işe yaradı mı?
Kyle Delaney

3
Sanırım asla bilemeyiz ...
Denny

12 yıl oldu ve bunlardan herhangi birinin işe yarayıp yaramadığını bilmiyoruz.
aroma

Yanıtlar:


222

SQL 2005 kullanıyorsanız böyle bir şey yapabilirsiniz ...

SELECT rs.Field1,rs.Field2 
    FROM (
        SELECT Field1,Field2, Rank() 
          over (Partition BY Section
                ORDER BY RankCriteria DESC ) AS Rank
        FROM table
        ) rs WHERE Rank <= 10

RankCriteria'nızın bağları varsa, 10'dan fazla satır döndürebilirsiniz ve Matt'in çözümü sizin için daha iyi olabilir.


31
İlk 10'u gerçekten istiyorsanız, Rank () yerine RowNumber () olarak değiştirin. O zaman bağ yok.
Mike L

3
Bu işe yarar, ancak ilk anahtarı RankCriteria olan bir dizin yoksa, rank () öğesinin sorgu planlayıcısı tarafından tam tablo sıralamasına dönüştürülmesinin olası olduğunu unutmayın. Bu durumda, farklı bölümleri seçerek daha iyi kilometre performansı elde edebilirsiniz ve RankCriteria desc.
Joe Kearney

Mükemmel cevap! Neredeyse ihtiyacım olanı aldım. DENSE_RANKNumaralandırmada herhangi bir boşluk bulunmadığı için sonuçlandım . +1
Michael Stramel

1
@ Facbed Sadece masadaki bir takma ad.
Darrel Miller

15
Sql Server kullanan herkes için, Mike L tarafından belirtilen RowNumber () işlevi ROW_NUMBER () şeklindedir.
randomraccoon

99

T-SQL'de yapardım:

WITH TOPTEN AS (
    SELECT *, ROW_NUMBER() 
    over (
        PARTITION BY [group_by_field] 
        order by [prioritise_field]
    ) AS RowNo 
    FROM [table_name]
)
SELECT * FROM TOPTEN WHERE RowNo <= 10

2
: Lütfen çözümünüz hakkında daha açıklayıcı olun. Bakınız: Nasıl Cevap
Verilir

CTE'deki select sorgusu nerede cümlesi içerebilir mi?
toha

1
@toha Evet yapabilir
KindaTechy

1
"In T-SQL" demenize rağmen bu ROW_NUMBERişlevi uygulayan herhangi bir veritabanı için çalışır. Örneğin, bu çözümü SQLite'de kullandım.
Tony

Postgres sql için de çalışır. Ben sadece "order by [Prioritise_field] desc" kullanmak zorunda
Phun

35

Bu SQL Server 2005'te çalışır (açıklamanızı yansıtacak şekilde düzenlenmiştir):

select *
from Things t
where t.ThingID in (
    select top 10 ThingID
    from Things tt
    where tt.Section = t.Section and tt.ThingDate = @Date
    order by tt.DateEntered desc
    )
    and t.ThingDate = @Date
order by Section, DateEntered desc

2
Ancak bu bölümün null olduğu satırlar için geçerli değildir. "Nerede (tt.Section boş ve t.Section boş) veya tt.Section = t.Section"
Matt Hamilton

29
SELECT r.*
FROM
(
    SELECT
        r.*,
        ROW_NUMBER() OVER(PARTITION BY r.[SectionID] ORDER BY r.[DateEntered] DESC) rn
    FROM [Records] r
) r
WHERE r.rn <= 10
ORDER BY r.[DateEntered] DESC

Diğer ad 'm' içeren tablo nedir?
Chalky

@Chalky yazım hatası olmalı r. sabit.
lorond

Bir cazibe gibi çalıştı. Teşekkür ederim!
Ron Nuni

18

Bunu şu şekilde yaparım:

SELECT a.* FROM articles AS a
  LEFT JOIN articles AS a2 
    ON a.section = a2.section AND a.article_date <= a2.article_date
GROUP BY a.article_id
HAVING COUNT(*) <= 10;

Güncelleme: Bu GROUP BY örneği yalnızca MySQL ve SQLite'de çalışır, çünkü bu veritabanları GROUP BY ile ilgili standart SQL'den daha izin vericidir. Çoğu SQL uygulaması, seçme listesindeki birleştirilmiş ifadenin parçası olmayan tüm sütunların da GROUP BY içinde olmasını gerektirir.


1
İşe yarıyor mu? Article_id hariç makalelerde her sütun için "a.somecolumn seçim listesinde bir toplama işlevi veya grup yan tümcesi içinde yer almadığı için geçersiz" eminim ..
Blorgbeard dışarı

1
GROUP BY içinde adlandırılan sütunlara işlevsel olarak bağımlı olan diğer sütunları dahil edebilmeniz gerekir. İşlevsel olarak bağımlı olmayan sütunlar belirsizdir. Ancak, RDBMS uygulamasına bağlı olarak haklısınız. MySQL'de çalışır ancak IIRC InterBase / Firebird'te başarısız olur.
Bill Karwin

1
Bir bölüm için ilk on bir kaydın hepsinin aynı tarihte olması durumunda bu işe yarar mı? Hepsinin sayısı 11 olur ve sonuç boş bir set olur.
Arth

Hayır, hepsi aynı tarihe sahipse bağları koparmanın bir yoluna ihtiyacınız var. Örnek için stackoverflow.com/questions/121387/… adresine bakın .
Bill Karwin

1
@carlosgg, makalelerin bölümlerle çoktan çoğa ilişkisi varsa, makaleleri bölümleriyle eşleştirmek için bir kesişim tablosuna ihtiyacınız olacaktır. Ardından, sorgunuzun m2m ilişkisi için bir kavşak tablosuna katılması ve article_id ve section'a göre gruplandırması gerekir. Bu işe başlamanız gerekir, ancak tüm çözümü bir yorumda yazmayacağım.
Bill Karwin

16

Biz bir SQL Server> = 2005, o zaman görev çözebilir kullanırsanız seçmek sadece:

declare @t table (
    Id      int ,
    Section int,
    Moment  date
);

insert into @t values
(   1   ,   1   , '2014-01-01'),
(   2   ,   1   , '2014-01-02'),
(   3   ,   1   , '2014-01-03'),
(   4   ,   1   , '2014-01-04'),
(   5   ,   1   , '2014-01-05'),

(   6   ,   2   , '2014-02-06'),
(   7   ,   2   , '2014-02-07'),
(   8   ,   2   , '2014-02-08'),
(   9   ,   2   , '2014-02-09'),
(   10  ,   2   , '2014-02-10'),

(   11  ,   3   , '2014-03-11'),
(   12  ,   3   , '2014-03-12'),
(   13  ,   3   , '2014-03-13'),
(   14  ,   3   , '2014-03-14'),
(   15  ,   3   , '2014-03-15');


-- TWO earliest records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment) <= 2 
        then 0 
        else 1 
    end;


-- THREE earliest records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment) <= 3 
        then 0 
        else 1 
    end;


-- three LATEST records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment desc) <= 3 
        then 0 
        else 1 
    end;

1
Kullanarak nasıl 1 's basitlik için bu çözümün gibi ama Açıklayabileceğini top 1ile çalışmalarını casegeçen ifade order by0 veya 1 dönen maddesi?
Ceres

3
TOP 1, burada Kravatlarla çalışır. WITH TIES, ORDER BY = 0 olduğunda, SELECT'in bu kaydı (TOP 1 nedeniyle) ve ORDER BY = 0'a sahip olan diğerlerinin (WITH TIES nedeniyle) aldığı anlamına gelir
Vadim Loboda

9

Bölümlerin ne olduğunu biliyorsanız, şunları yapabilirsiniz:

select top 10 * from table where section=1
union
select top 10 * from table where section=2
union
select top 10 * from table where section=3

3
Bunu yapmanın en kolay yolu bu olurdu.
Hector Sosa Jr

3
Ancak, 150'niz varsa veya kategoriler güne, haftaya vb. Göre
değişiyorsa

1
Tabii, ama OP alıntılamak için: "Bölümler iş, yerel ve özelliktir". Üç statik kategoriniz varsa, bunu yapmanın en iyi yolu budur.
Blorgbeard

9

Bu konu biraz eski olduğunu biliyorum ama ben sadece benzer bir sorun (her kategoriden en yeni makaleyi seçin) içine çarptı ve bu ile geldi çözüm:

WITH [TopCategoryArticles] AS (
    SELECT 
        [ArticleID],
        ROW_NUMBER() OVER (
            PARTITION BY [ArticleCategoryID]
            ORDER BY [ArticleDate] DESC
        ) AS [Order]
    FROM [dbo].[Articles]
)
SELECT [Articles].* 
FROM 
    [TopCategoryArticles] LEFT JOIN 
    [dbo].[Articles] ON
        [TopCategoryArticles].[ArticleID] = [Articles].[ArticleID]
WHERE [TopCategoryArticles].[Order] = 1

Bu, Darrel'in çözümüne çok benzer, ancak amaçlandığından daha fazla satır getirebilecek RANK sorununun üstesinden gelir.


Neden CTE Sir kullanıyorsunuz? Bellek tüketimini azaltır mı?
toha

@toha çünkü CTE'ler daha basit ve anlaşılması daha kolay
Reversed Engineer

Mükemmel cevap!! Karşılıklı kayıt olmadan hiçbir zaman bir kayıt olmayacağından, JOINbunun yerine iç kısım kullanılarak optimize LEFT JOINedilebilir . TopCategoryArticlesArticle
Ters Mühendis

6

Aşağıdakileri denedim ve bağlarla da çalıştı.

SELECT rs.Field1,rs.Field2 
FROM (
    SELECT Field1,Field2, ROW_NUMBER() 
      OVER (Partition BY Section
            ORDER BY RankCriteria DESC ) AS Rank
    FROM table
    ) rs WHERE Rank <= 10

5

Bölümlere göre gruplandırılmış çıktılar üretmek istiyorsanız , her bölümden yalnızca en üstteki n kaydı görüntülemek gibi bir şey:

SECTION     SUBSECTION

deer        American Elk/Wapiti
deer        Chinese Water Deer
dog         Cocker Spaniel
dog         German Shephard
horse       Appaloosa
horse       Morgan

... sonra aşağıdakiler tüm SQL veritabanlarıyla oldukça genel olarak çalışmalıdır. İlk 10'u istiyorsanız, sorgunun sonuna doğru 2 değerini 10 olarak değiştirin.

select
    x1.section
    , x1.subsection
from example x1
where
    (
    select count(*)
    from example x2
    where x2.section = x1.section
    and x2.subsection <= x1.subsection
    ) <= 2
order by section, subsection;

Kurmak:

create table example ( id int, section varchar(25), subsection varchar(25) );

insert into example select 0, 'dog', 'Labrador Retriever';
insert into example select 1, 'deer', 'Whitetail';
insert into example select 2, 'horse', 'Morgan';
insert into example select 3, 'horse', 'Tarpan';
insert into example select 4, 'deer', 'Row';
insert into example select 5, 'horse', 'Appaloosa';
insert into example select 6, 'dog', 'German Shephard';
insert into example select 7, 'horse', 'Thoroughbred';
insert into example select 8, 'dog', 'Mutt';
insert into example select 9, 'horse', 'Welara Pony';
insert into example select 10, 'dog', 'Cocker Spaniel';
insert into example select 11, 'deer', 'American Elk/Wapiti';
insert into example select 12, 'horse', 'Shetland Pony';
insert into example select 13, 'deer', 'Chinese Water Deer';
insert into example select 14, 'deer', 'Fallow';

Her bölüm için sadece ilk kaydı istediğimde bu çalışmaz. 1'den fazla kaydı olan tüm bölüm gruplarını ortadan kaldırır. <= 2, <= 1
nils

@nils Yalnızca üç bölüm değeri vardır: geyik, köpek ve at. Sorguyu <= 1 olarak değiştirirseniz, her bölüm için bir alt bölüm alırsınız: Geyik için Amerikan Elk / Wapiti, köpek için Cocker Spaniel ve at için Appaloosa. Bunlar aynı zamanda her bölümdeki alfabetik olarak ilk değerlerdir. Sorgu, diğer tüm değerleri ortadan kaldırmak içindir .
Craig

Ancak sorgunuzu çalıştırmayı denediğimde, her şey ortadan kalkar çünkü sayım her şey için> = 1'dir. Her bölüm için 1. alt bölümü korumaz. <= 1 için sorgunuzu çalıştırmayı deneyebilir ve her bölüm için ilk alt bölümü alıp almadığınızı söyleyebilir misiniz?
nils

@nils Merhaba, bu küçük test veritabanını komut dosyalarından yeniden oluşturdum ve <= 1 kullanarak sorguyu çalıştırdım ve her bölümden ilk alt bölüm değerini döndürdü. Hangi veritabanı sunucusunu kullanıyorsunuz? Seçim veritabanınızla ilgili her zaman bir şans vardır. Ben sadece bu MySQL koştu çünkü kullanışlı ve beklendiği gibi davrandı. Ben ilk kez (ben gerçekten ne hata ayıklama olmadan çalıştığından emin olmak istedim) eminim ben oldukça eminim, ben Sybase SQL Anywhere veya MS SQL Server kullanarak yaptı eminim.
Craig

MySQL'de benim için mükemmel çalıştı. Neden o alt bölümdeki varchar alanı için kullanılan <= emin değilim bir sorgu biraz değiştirdim .. i ve bunu değiştirdi x2.subsection = x1.subsection
Mahen Nakar

4

Olabilir BİRLİĞİ sizin için operatör çalışır? Her bölüm için bir SEÇİN, sonra bir araya getirin. Sanırım bu sadece belirli sayıda bölüm için işe yarayacaktır.


4

S) Her gruptan TOP X kayıtlarını bulma (Oracle)

SQL> select * from emp e 
  2  where e.empno in (select d.empno from emp d 
  3  where d.deptno=e.deptno and rownum<3)
  4  order by deptno
  5  ;

 EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO

  7782 CLARK      MANAGER         7839 09-JUN-81       2450                    10
  7839 KING       PRESIDENT            17-NOV-81       5000                    10
  7369 SMITH      CLERK           7902 17-DEC-80        800                    20
  7566 JONES      MANAGER         7839 02-APR-81       2975                    20
  7499 ALLEN      SALESMAN        7698 20-FEB-81       1600        300         30
  7521 WARD       SALESMAN        7698 22-FEB-81       1250        500         30

6 satır seçildi.



Soru Oracle ile değil SQL Server ile ilgiliydi.
Craig

2

Soru SQL Server 2005 ile ilgili olsa da, çoğu insan devam etti ve bu soruyu bulurlarsa, diğer durumlarda tercih edilen cevap bu blog yazısında gösterildiği gibi kullanmaktırCROSS APPLY .

SELECT *
FROM t
CROSS APPLY (
  SELECT TOP 10 u.*
  FROM u
  WHERE u.t_id = t.t_id
  ORDER BY u.something DESC
) u

Bu sorgu 2 tablo içerir. OP sorgusu sadece bir tablo içerir, bu durumda bir pencere fonksiyon tabanlı çözüm daha verimli olabilir.


1

Bu yaklaşımı deneyebilirsiniz. Bu sorgu, her ülke için en kalabalık 10 şehri döndürür.

   SELECT city, country, population
   FROM
   (SELECT city, country, population, 
   @country_rank := IF(@current_country = country, @country_rank + 1, 1) AS country_rank,
   @current_country := country 
   FROM cities
   ORDER BY country, population DESC
   ) ranked
   WHERE country_rank <= 10;

Bu çözüm, 9 aynı nüfusa sahip bir ülkenin kaydını içeren bir tabloya sahip olduğumuzda bir test durumunu geçmez, örneğin mevcut 9 kaydın tümünü sırayla döndürmek yerine null değerini döndürür. Bu sorunu çözmek için herhangi bir öneriniz var mı?
Mojgan Mazouchi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.