Лучшие k учеников в каждом классе в MySQL - PullRequest
1 голос
/ 10 января 2012

У меня есть две таблицы, которые выглядят так:

Class  Name  Score  Top
1      Amy    90     X
1      Ben    70     X
1      Chu    80     X
2      Don    60     X
2      Elf    65     X
2      Fez    75     X
2      Ges    35     X
2      Han    40     X

Class NumToppers
1      2
2      3

Я хочу найти Top "NumToppers" из каждого класса и соответственно обновить поле "Top":

Class  Name  Score  Top
1      Amy    90     Y
1      Ben    70     N
1      Chu    80     Y
2      Don    60     Y
2      Elf    65     Y
2      Fez    75     Y
2      Ges    35     N
2      Han    40     N

Iу меня есть 100 "классов" в моих реальных данных.Так что, хотя данные выглядят забавными, пожалуйста, никаких игрушечных решений.

1 Ответ

3 голосов
/ 10 января 2012

Обращаясь к на этой превосходной странице по выбору 'great-n-per-group' , вот запрос, который я придумал.Оглядываясь назад, он в основном идентичен ответу @ BassamMehanni, за исключением того, что в MySQL нет функции ROW_NUMBER().

Это предполагает, что у вас есть таблицы class и toppers.

Решение:

Примечание: если у вас есть основной идентификатор в таблице class, который не является составной комбинацией (Class,Name,Score), используйте его вместо этого для условия объединения, помеченного #@@.

set @class='';
set @rank=1;
UPDATE class         
LEFT JOIN 
  (SELECT Class,Name,Score,
       @rank:=if(@class=Class,@rank+1,1) as rank, 
       @class:=Class as dummy 
  FROM class ORDER BY Class,Score DESC) c
ON c.Class=class.Class AND c.Score=class.Score  #@@
   AND c.Name=class.Name                        #@@
LEFT JOIN toppers
ON c.Class=toppers.Class
SET Top = (CASE WHEN rank <= NumToppers THEN 'Y' ELSE 'N' END); 

Объяснение

По сути, этот запрос:

  1. нумерует строки по class сверху вниз по счету в каждом классе.То есть оценивает каждого ученика в каждом классе.
  2. выбирает строки class, для которых rank равно <= NumToppers, для каждого класса.
  3. Обновляет их.

Для шага 1 см. Следующее (по ссылке, на которую я вас ссылался):

set @class='';
set @rank=1;
SELECT Class,Name,Score, 
       @num:=if(@class=Class,@rank+1,1) as rank, 
       @class:=Class as dummy 
FROM class ORDER BY Class,Score DESC;

Это просматривает каждую строку class (после сортировки по классу и убыванию оценки) иустанавливает rank в 1, если мы находимся в новом классе, или rank+1, если мы в одном и том же классе.

Для шага 2 мы выполняем JOIN с toppers наclass и выберите верхние NumToppers строки для каждого класса:

set @class='';
set @rank=1;
SELECT *                                          # NEW
FROM toppers                                      # NEW
LEFT JOIN                                         # NEW
  (SELECT Class,Name,Score,                       #\
       @rank:=if(@class=Class,@rank+1,1) as rank, # |(same as step 1)
       @class:=class as dummy                     # |
  FROM class ORDER BY Class,Score DESC) c         #/ 
ON c.Class=toppers.Class                          # NEW
WHERE rank <= NumToppers;                         # NEW

Наконец, мы обновим эти условия (шаг 3).Однако мы должны сделать UPDATE class явно, поэтому мы должны добавить дополнительно JOIN шага 2 с class:

set @class='';
set @rank=1;
UPDATE class                                      # NEW
LEFT JOIN 
  (SELECT Class,Name,Score,
       @rank:=if(@class=Class,@rank+1,1) as rank, 
       @class:=class as dummy 
  FROM class ORDER BY Class,Score DESC) c
ON c.Class=class.Class AND c.Score=class.Score    # NEW (join condition)
   AND c.Name=class.Name                          # NEW (join condition)
LEFT JOIN toppers
ON c.Class=toppers.Class
SET top = (CASE WHEN rank <= NumToppers THEN 'Y' ELSE 'N' END); # NEW

Если у вас есть основной идентификатор на class, который предпочтительноне составной (т. е. идентификатор из одного столбца), а затем присоединитесь к нему.

...