Как выбрать ТОП 5 ПРОЦЕНТОВ из каждой группы? - PullRequest
12 голосов
/ 28 сентября 2011

У меня есть пример таблицы:

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

Дает мне следующее:

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

Теперь, как мне выбрать TOP 5 PERCENT записей из каждой категории при условии, что в каждой категории более 100 записей (не показанных здесь в таблице примеров) ? Например, в моей фактической таблице следует удалить запись John из A и Lily запись из B в зависимости от ситуации (опять же, я не показывал полную таблицу здесь), чтобы получить:

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

Я пытался использовать предложения CTE s и PARTITION BY, но не могу достичь того, чего хочу. Он удаляет ТОП-5 ПРОЦЕНТОВ от общего результата, но не из каждой категории. Есть предложения?

Ответы [ 4 ]

16 голосов
/ 28 сентября 2011

Вы можете использовать CTE (Common Table Expression) в паре с оконной функцией NTILE - это разделит ваши данные на столько срезов, сколько вам нужно, например, в вашем случае на 20 ломтиков (каждый 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

Это в основном группирует ваши данные по Category,Name, упорядочивает их по чему-то другому (не уверен, действительно ли вам нужна здесь COUNT(Name)), а затем разбивает их на 20 частей, каждая из которых представляет 5% вашего раздела данных. , Срез с NTile = 1 является верхним 5% срезом - просто игнорируйте это при выборе из CTE.

См:

для получения дополнительной информации

1 голос
/ 15 августа 2012
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

Выход:

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

Надеюсь, это поможет:)

1 голос
/ 28 сентября 2011

Редактировать: я добавил второе решение

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

Результаты:

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

или

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

Результаты:

Category Name       CategoryNameCount
-------- ---------- -----------------
A        Adam       4
A        Lisa       2
A        Bucky      1
B        Tom        4
B        Ross       3
0 голосов
/ 29 ноября 2017
;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 не будет работать, если количество записей меньше вашего номера плитки.

...