2011-09-28 14 views
11

Ho una tabella di esempio come questo:Come selezionare TOP 5 PERCENT da ciascun gruppo?

CREATE TABLE #TEMP(Category VARCHAR(100), Name VARCHAR(100)) 

INSERT INTO #TEMP VALUES('A', 'John') 
INSERT INTO #TEMP VALUES('A', 'John') 
INSERT INTO #TEMP VALUES('A', 'John') 
INSERT INTO #TEMP VALUES('A', 'John') 
INSERT INTO #TEMP VALUES('A', 'John') 
INSERT INTO #TEMP VALUES('A', 'John') 
INSERT INTO #TEMP VALUES('A', 'Adam') 
INSERT INTO #TEMP VALUES('A', 'Adam') 
INSERT INTO #TEMP VALUES('A', 'Adam') 
INSERT INTO #TEMP VALUES('A', 'Adam') 
INSERT INTO #TEMP VALUES('A', 'Lisa') 
INSERT INTO #TEMP VALUES('A', 'Lisa') 
INSERT INTO #TEMP VALUES('A', 'Bucky') 
INSERT INTO #TEMP VALUES('B', 'Lily') 
INSERT INTO #TEMP VALUES('B', 'Lily') 
INSERT INTO #TEMP VALUES('B', 'Lily') 
INSERT INTO #TEMP VALUES('B', 'Lily') 
INSERT INTO #TEMP VALUES('B', 'Lily') 
INSERT INTO #TEMP VALUES('B', 'Tom') 
INSERT INTO #TEMP VALUES('B', 'Tom') 
INSERT INTO #TEMP VALUES('B', 'Tom') 
INSERT INTO #TEMP VALUES('B', 'Tom') 
INSERT INTO #TEMP VALUES('B', 'Ross') 
INSERT INTO #TEMP VALUES('B', 'Ross') 
INSERT INTO #TEMP VALUES('B', 'Ross') 

SELECT Category, Name, COUNT(Name) Total 
FROM #TEMP 
GROUP BY Category, Name 
ORDER BY Category, Total DESC 

DROP TABLE #TEMP 

mi dà il seguente:

A John 6 
A Adam 4 
A Lisa 2 
A Bucky 1 
B Lily 5 
B Tom  4 
B Ross 3 

Ora, come faccio a selezionare i TOP 5 PERCENT record da ogni categoria assumendo ogni categoria ha più di 100 record (non mostrato nella tabella di esempio qui)? Per esempio, nella mia tabella effettiva, si deve rimuovere il record John da A e Lily registrare da B a seconda dei casi (ancora una volta, non ho vedere la tabella completa qui) per ottenere:

A Adam 4 
A Lisa 2 
A Bucky 1 
B Tom  4 
B Ross 3 

ho cercato di utilizzare le clausole CTE e PARTITION BY ma non riesco a ottenere ciò che voglio. Rimuove il 5 PERCENT TOP dal risultato complessivo ma non da ogni categoria. Eventuali suggerimenti?

+1

Può aiutare in un modo piccolo - Se si dispone di un conteggio per un gruppo, ricordare che il 5 percento sarebbe "row_num <= (5 * count)/100" –

+0

@KierenJohnstone: +1 Grazie. So che potrei dover usare CROSS APPLY o qualcosa di simile ma ho ancora qualche problema. Aggiornerà se lo capisco. – Legend

+1

Qual è l'uscita desiderata quindi per favore? Rimuovi il 5% superiore superiore è molto piccolo rispetto al conteggio di 6. Una riga (A, John) è il 16%. – gbn

risposta

14

È possibile utilizzare un CTE (Common Table Expression) abbinato alla funzione di finestratura , che suddividerà i dati in tutte le fette necessarie, ad es. nel tuo caso, in 20 fette (ogni 5%).

;WITH SlicedData AS 
(
    SELECT Category, Name, COUNT(Name) Total, 
      NTILE(20) OVER(PARTITION BY Category ORDER BY COUNT(Name) DESC) AS 'NTile' 
    FROM #TEMP 
    GROUP BY Category, Name 
) 
SELECT * 
FROM SlicedData 
WHERE NTile > 1 

Questo fondamentalmente gruppi dati da parte di Category,Name, ordini da qualcos'altro (non so se COUNT(Name) è davvero la cosa si vuole qui), e poi le fette in su in 20 pezzi, ognuno dei quali rappresenta il 5% della vostra partizione dati . La fetta con NTile = 1 è la fetta superiore del 5%: ignorala quando selezioni dal CTE.

See:

per ulteriori informazioni

+1

Questo serve al mio scopo. Grazie mille. Ho corretto il tuo post per alcune parti mancanti nella query per renderlo pronto all'uso. – Legend

+0

@Legend Ho pensato che volevi rimuovere i record, non solo selezionarli? –

+0

@TimRogers: sicuro. Ho appena creato un elenco di esclusioni utilizzando questa query per i nomi che volevo rimuovere. Cercherò di risolvere la mia domanda. – Legend

1

Edit: ho aggiunto la seconda soluzione

SELECT b.Id 
     ,b.Category 
     ,b.Name 
     ,b.CategoryNameCount 
FROM 
(
     SELECT a.Id 
       ,a.Category 
       ,a.Name 
       ,COUNT(*)OVER(PARTITION BY a.Category, a.Name) CategoryNameCount 
       ,COUNT(*)OVER(PARTITION BY a.Category) CategoryCount 
     FROM #TEMP a 
) b 
WHERE b.CategoryCount*5.0/100 > b.CategoryCount*b.CategoryNameCount*1.0/100 
ORDER BY b.Category, b.CategoryNameCount DESC, b.Name 

Risultati:

Id   Category Name  CategoryNameCount 
----------- -------- ---------- ----------------- 
7   A  Adam  4 
8   A  Adam  4 
9   A  Adam  4 
10   A  Adam  4 
11   A  Lisa  2 
12   A  Lisa  2 
13   A  Bucky  1 
19   B  Tom  4 
20   B  Tom  4 
21   B  Tom  4 
22   B  Tom  4 
23   B  Ross  3 
24   B  Ross  3 
25   B  Ross  3 

o

SELECT b.Category, b.Name, b.CategoryNameCount 
FROM 
(
     SELECT 
       a.Category 
       ,a.Name 
       ,COUNT(*)OVER(PARTITION BY a.Category, a.Name) CategoryNameCount 
       ,COUNT(*)OVER(PARTITION BY a.Category) CategoryCount 
     FROM #TEMP a 
) b 
WHERE b.CategoryCount*5.0/100 > b.CategoryCount*b.CategoryNameCount*1.0/100 
GROUP BY b.Category, b.Name, b.CategoryNameCount 
ORDER BY b.Category, b.CategoryNameCount DESC, b.Name 

risultati:

Category Name  CategoryNameCount 
-------- ---------- ----------------- 
A  Adam  4 
A  Lisa  2 
A  Bucky  1 
B  Tom  4 
B  Ross  3 
+0

+1 Grazie per il tuo tempo. Non ho una colonna 'Id', ma credo di poterla aggiungere facilmente. – Legend

+0

Spero che queste nuove soluzioni siano migliori. –

+0

Dolce! Grazie mille per il tuo tempo :) Pubblicherò il confronto delle prestazioni, se possibile, sul mio DB. – Legend

1
select Category,name,CountTotal,RankSeq,(50*CountTotal)/100 from (
select Category,name,COUNT(*) 
over (partition by Category,name) as CountTotal, 
ROW_NUMBER() 
over (partition by Category,name order by Category) RankSeq from #TEMP 
--group by Category,Name 
) temp 
where RankSeq <= ((50*CountTotal)/100) 
order by Category,Name,RankSeq 

uscita:

01.235.164,106174 millions
Category name  CountTotal RankSeq  50*CountTotal)/100 
A   Adam  4   1   2 
A   Adam  4   2   2 
A   John  6   1   3 
A   John  6   2   3 
A   John  6   3   3 
A   Lisa  2   1   1 
B   Lily  5   1   2 
B   Lily  5   2   2 
B   Ross  3   1   1 
B   Tom  4   1   2 
B   Tom  4   2   2 

Spero che questo aiuta :)

0
;WITH SlicedData AS 
(
    SELECT Category, Name, COUNT(Name) Total, 
      **PERCENT_RANK() OVER(PARTITION BY Category ORDER BY COUNT(Name) DESC) * 100** AS 'Percent' 
    FROM #TEMP 
    GROUP BY Category, Name 
) 
SELECT * 
FROM SlicedData 
WHERE Percent < 5 

NTILE non funziona se il numero di record è inferiore al tuo numero di tessera.

Problemi correlati