Seleziona i primi 10 record per ogni categoria


208

Voglio restituire i primi 10 record di ogni sezione in una query. Qualcuno può aiutare con come farlo? La sezione è una delle colonne nella tabella.

Il database è SQL Server 2005. Voglio restituire i primi 10 per data inserita. Le sezioni sono aziendali, locali e di funzionalità. Per una data particolare voglio solo le prime (10) righe aziendali (voce più recente), le prime (10) righe locali e le prime (10) funzionalità.


Qualcuno di queste risposte ha funzionato per te?
Kyle Delaney,

3
Immagino che non lo sapremo mai ...
Denny,

Sono passati 12 anni e non sappiamo se qualcuno di quelli ha funzionato.
aroma

Risposte:


222

Se stai usando SQL 2005 puoi fare qualcosa del genere ...

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

Se il tuo RankCriteria ha legami, puoi restituire più di 10 righe e la soluzione di Matt potrebbe essere migliore per te.


31
Se vuoi davvero solo i primi 10, cambiali in RowNumber () invece di Rank (). Nessun legame allora.
Mike L

3
Funziona, ma tieni presente che è probabile che rank () venga trasformato in un ordinamento di tabella completa dal pianificatore di query se non esiste un indice la cui prima chiave è RankCriteria. In questo caso è possibile ottenere un chilometraggio migliore selezionando le sezioni distinte e applicare la domanda incrociata per selezionare i primi 10 ordinati da Desc Rankrriteria.
Joe Kearney,

Bella risposta! Mi ha fatto quasi esattamente quello di cui avevo bisogno. Alla fine ho deciso di DENSE_RANKnon avere lacune nella numerazione. +1
Michael Stramel,

1
@Facbed È solo un alias sul tavolo.
Darrel Miller,

15
Per chiunque utilizzi Sql Server, la funzione RowNumber () menzionata da Mike L è ROW_NUMBER ().
randomraccoon,

99

In T-SQL, farei:

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
: Si prega di essere più descrittivi sulla soluzione. Consultare: Come rispondere
askmish

La query di selezione in CTE può contenere la clausola where?
Toha,

1
@toha Sì, può
KindaTechy,

1
Sebbene tu dica "In T-SQL" funziona per qualsiasi database che implementa la ROW_NUMBERfunzione. Ad esempio, ho usato questa soluzione in SQLite.
Tony,

Funziona anche con Postgres sql. Ho dovuto usare "ordina per [prioritise_field] desc"
Phun

35

Funziona su SQL Server 2005 (modificato per riflettere i tuoi chiarimenti):

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
Tuttavia, ciò non funziona per le righe in cui la sezione è nulla. Dovresti dire "dove (tt.Section è null e t.Section è null) o 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

Cos'è la tabella con l'alias 'm'?
Chalky

@Chalky è un errore di battitura, dovrebbe essere r. fisso.
lorond

Ha funzionato come un fascino. Grazie!
Ron Nuni,

18

Lo faccio in questo modo:

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;

aggiornamento: questo esempio di GROUP BY funziona solo in MySQL e SQLite, poiché tali database sono più permissivi rispetto allo standard SQL per GROUP BY. La maggior parte delle implementazioni SQL richiede che tutte le colonne dell'elenco di selezione che non fanno parte di un'espressione aggregata si trovino anche in GROUP BY.


1
Funziona? Sono abbastanza sicuro che "a.somecolumn non è valido nell'elenco di selezione in quanto non è contenuto in una funzione aggregata o nella clausola group by" per ogni colonna negli articoli tranne article_id ..
Blorgbeard è uscito il

1
Dovresti essere in grado di includere altre colonne che sono funzionalmente dipendenti dalle colonne nominate in GROUP BY. Le colonne che non sono funzionalmente dipendenti sono ambigue. Ma hai ragione, a seconda dell'implementazione di RDBMS. Funziona in MySQL ma IIRC fallisce in InterBase / Firebird.
Bill Karwin,

1
Funzionerebbe nel caso in cui i primi undici record di una sezione avessero tutti la stessa data? Avrebbero tutti conteggi di 11 e il risultato sarebbe un set vuoto.
Arth,

No, devi avere un modo per rompere i legami se hanno tutti la stessa data. Vedere stackoverflow.com/questions/121387/… per un esempio.
Bill Karwin,

1
@carlosgg, se gli articoli hanno una relazione molti-a-molti con le sezioni, allora dovresti avere una tabella di intersezione per mappare gli articoli alle loro sezioni. Quindi la query dovrebbe unirsi a una tabella di intersezione per la relazione m2m e raggruppare per articolo_id e sezione. Questo dovrebbe iniziare, ma non scriverò l'intera soluzione in un commento.
Bill Karwin,

16

Se utilizziamo SQL Server> = 2005, possiamo risolvere l'attività con una sola selezione :

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
+1 Mi piace questa soluzione per la sua semplicità, ma potresti spiegare come top 1funziona con l' caseistruzione nella order byclausola che restituisce 0 o 1?
Cerere,

3
TOP 1 funziona con WITH TIES qui. WITH TIES significa che quando ORDER BY = 0, SELECT seleziona questo record (a causa di TOP 1) e tutti gli altri che hanno ORDER BY = 0 (a causa di WITH TIES)
Vadim Loboda,

9

Se sai quali sono le sezioni, puoi fare:

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
Questo sarebbe il modo più semplice per farlo.
Hector Sosa Jr,

3
Ma questo sarebbe inefficiente se ne hai 150 o se le categorie sono variabili per giorno, settimana, ecc.
Rafa Barragan,

1
Certo, ma per citare OP: "Le sezioni sono affari, locali e funzionalità". Se hai tre categorie statiche, questo è il modo migliore per farlo.
Blorgbeard esce l'

9

So che questo thread è un po 'vecchio, ma ho appena incontrato un problema simile (seleziona l'articolo più recente da ogni categoria) e questa è la soluzione che ho trovato:

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

Questo è molto simile alla soluzione di Darrel ma risolve il problema RANK che potrebbe restituire più righe del previsto.


Perché usare CTE Sir? Riduce il consumo di memoria?
Toha,

@toha perché i CTE sono più semplici e più comprensibili
Reversed Engineer

Bella risposta!! Potrebbe essere ottimizzato usando inner JOINinvece di LEFT JOIN, poiché non ci sarà mai un record TopCategoryArticlessenza un Articlerecord corrispondente .
Reversed Engineer

6

Ho provato quanto segue e ha funzionato anche con i legami.

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

Se si desidera produrre output raggruppati per sezione, visualizzando solo i primi n record di ciascuna sezione in questo modo:

SECTION     SUBSECTION

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

... quindi quanto segue dovrebbe funzionare in modo abbastanza generico con tutti i database SQL. Se si desidera la top 10, basta cambiare da 2 a 10 verso la fine della query.

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;

Impostare:

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';

Questo non funziona quando voglio solo il primo record per ogni sezione. Elimina tutti i gruppi di sezioni che hanno più di 1 record. Ho provato sostituendo <= 2 con <= 1
nils il

@nils Ci sono solo tre valori di sezione: cervo, cane e cavallo. Se cambi la query in <= 1, otterrai una sottosezione per ogni sezione: American Elk / Wapiti per cervi, Cocker Spaniel per cane e Appaloosa per cavallo. Questi sono anche i primi valori in ogni sezione in ordine alfabetico. La query ha lo scopo di eliminare tutti gli altri valori.
Craig,

Ma quando provo ad eseguire la tua query, elimina tutto perché il conteggio è> = 1 per tutto. Non conserva la prima sottosezione per ogni sezione. Puoi provare a eseguire la tua query per <= 1 e fammi sapere se ottieni la prima sottosezione per ogni sezione?
NILS

@nils Ciao, ho ricreato questo piccolo database di test dagli script ed ho eseguito la query usando <= 1, e ha restituito il primo valore di sottosezione da ogni sezione. Quale server di database stai usando? C'è sempre la possibilità che sia correlato al tuo database preferito. Ho appena eseguito questo in MySQL perché era utile e si comportava come previsto. Sono abbastanza sicuro quando l'ho fatto la prima volta (volevo assicurarmi che ciò che ho pubblicato funzionasse davvero senza debuggin), sono abbastanza sicuro di averlo fatto usando Sybase SQL Anywhere o MS SQL Server.
Craig,

ha funzionato perfettamente per me in mysql. Ho cambiato un po 'una query non sono sicuro del perché abbia usato <= per il campo varchar nella sottosezione .. L'ho cambiato in e x2.subsection = x1.subsection
Mahen Nakar

4

L' operatore UNION potrebbe lavorare per te? Avere un SELEZIONA per ogni sezione, quindi UNION insieme. Immagino che funzionerebbe solo per un numero fisso di sezioni però.


4

Q) Trovare i record TOP X da ciascun gruppo (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 righe selezionate.



La domanda riguardava SQL Server, non Oracle.
Craig,

2

Mentre la domanda riguardava SQL Server 2005, la maggior parte delle persone è passata e se trovano questa domanda, quale potrebbe essere la risposta preferita in altre situazioni è quella che si usa CROSS APPLYcome illustrato in questo post del blog .

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

Questa query coinvolge 2 tabelle. La query del PO coinvolge solo 1 tabella, nel caso in cui una soluzione basata sulla funzione finestra possa essere più efficiente.


1

Puoi provare questo approccio. Questa query restituisce le 10 città più popolate per ogni paese.

   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;

Questa soluzione non supera un caso di test quando abbiamo una tabella con un record di un paese con 9 stessa popolazione, ad esempio restituisce null invece di restituire tutti i 9 record disponibili in ordine. Qualche suggerimento per risolvere questo problema?
Mojgan Mazouchi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.