2014-09-07 3 views
1

Допустим, у меня есть следующая таблица:MySQL найти пользователя ранг для каждой категории

user_id | category_id | points 
------------------------------- 
     1 |   1 | 4 
     2 |   1 | 2 
     2 |   1 | 5 
     1 |   2 | 3 
     2 |   2 | 2 
     1 |   3 | 1 
     2 |   3 | 4 
     1 |   3 | 8 

Может кто-то пожалуйста, помогите мне построить запрос, возвращающий ранг пользователя в категории - что-то вроде этого:

user_id | category_id | total_points | rank 
------------------------------------------- 
     1 |   1 |   4 | 2 
     1 |   2 |   3 | 1 
     1 |   3 |   9 | 1 
     2 |   1 |   7 | 1 
     2 |   2 |   2 | 2 
     2 |   3 |   4 | 2 
+0

Используйте SELECT user_id, category_id, sum (points) как total_points FROM table GROUP BY user_id, category_id для первых 3 столбцов ... –

+0

Посмотрите эту скрипту: http://www.sqlfiddle.com/#!2/317da3/2 –

ответ

3

Во-первых, вам нужно получить общее количество баллов за каждую категорию. Затем вам нужно перечислить их. В MySQL это легко делается с переменными:

SELECT user_id, category_id, points, 
     (@rn := if(@cat = category_id, @rn + 1, 
        if(@cat := category_id, 1, 1) 
       ) 
     ) as rank 
FROM (SELECT u.user_id, u.category_id, SUM(u.points) as points 
     FROM users u 
     GROUP BY u.user_id, u.category_id 
    ) g cross join 
    (SELEct @user := -1, @cat := -1, @rn := 0) vars 
ORDER BY category_id, points desc; 
+0

Это демонстрирует использование user_variables, но ... предложение GROUP BY в 'g' должно быть' u.category_id , u.user_id'. И нет необходимости инициализировать '@ user'. (Меня смутило второе выражение IF в «rank», но потом я понял, что на самом деле не предназначено быть условным тестом, именно там происходит назначение категории_id для пользовательской переменной.) – spencer7593

+0

@ spencer7593. , , Эти проблемы исправлены. Цель вложенного 'if' заключается в том, что MySQL не гарантирует порядок оценки выражений, поэтому все назначения переменных находятся в одном выражении. –

+0

+1 Правильно, я понимаю, что MySQL не гарантирует порядок оценки; MySQL не гарантирует, что пользовательские переменные будут «работать» так, как мы их видим. Хороший трюк, чтобы помещать это назначение в выражение 'IF(). (Подход, который я использую, - это упорядочить выражения в списке SELECT, чтобы столбец 'category_id' после столбца' rank', и я выполняю назначение в строке, которая возвращает столбец 'category_id', но мне нравится подход, который вы используете – spencer7593

1

Вы хотите получить SUM точек для каждого уникального category_id:

SELECT u.user_id, u.category_id, SUM(u.points) 
FROM users AS u 
GROUP BY uc.category_id 
0

MySQL не имеет аналитические функции, как и другие базы данных (Oracle, SQL Server), которые были бы очень удобно для возвращения результата, как это.

Первые три столбца просты, всего GROUP BY user_id, category_id и SUM(points).

Получение возвращаемого столбца rank является немного более сложной задачей. Помимо этого на клиенте, если вам нужно сделать это в инструкции SQL, вы можете использовать переменные, определенные пользователем MySQL.

SELECT @rank := IF(@prev_category = r.category_id, @rank+1, 1) AS rank 
    , @prev_category := r.category_id AS category_id 
    , r.user_id 
    , r.total_points 
    FROM (SELECT @prev_category := NULL, @rank := 1) i 
CROSS 
    JOIN (SELECT s.category_id, s.user_id, SUM(s.points) AS total_points 
      FROM users s 
     GROUP BY s.category_id, s.user_id 
     ORDER BY s.category_id, total_points DESC 
     ) r 
ORDER BY r.category_id, r.total_points DESC, r.user_id DESC 

Целью зрения инлайн с псевдонимом i, чтобы инициализировать пользовательские переменные. Встроенный просмотр с псевдонимом как r возвращает total_points для каждого (user_id, category_id).

«Трюк» - это сравнить значение category_id предыдущей строки со значением текущей строки; если они совпадают, мы увеличиваем ранг на 1. Если это «новая» категория, мы возвращаем ранг на 1. Обратите внимание, что это работает только в том случае, если строки упорядочены по категориям, а затем по итоговым точкам, по убыванию, поэтому нам нужен ORDER BY пункт. Также обратите внимание, что порядок выражений в списке SELECT важен; нам нужно выполнить сравнение предыдущего значения до того, как оно будет перезаписано текущим значением, поэтому присвоение @prev_category должно соответствовать условному тесту.

Также обратите внимание, что если у двух пользователей одинаковые тотальные точки в категории, они получат разные значения для ранга ... указанный выше запрос не дает того же ранга для галстука. (Запрос можно также изменить, чтобы сделать это, но нам также необходимо сохранить total_points из предыдущей строки, поэтому мы можем сравнить с текущей строкой.

Также обратите внимание, что этот синтаксис специфичен для MySQL и что это поведение не гарантировано.


Если вам нужны столбцы в определенной последовательности и/или строки в определенном порядке (чтобы получить точный набор результатов указанных), мы должны обернуть запрос, приведенный выше, как встроенный вид.

SELECT t.user_id 
    , t.category_id 
    , t.total_points 
    , t.rank 
    FROM (
     SELECT @rank := IF(@prev_category = r.category_id, @rank+1, 1) AS rank 
       , @prev_category := r.category_id AS category_id 
       , r.user_id 
       , r.total_points 
      FROM (SELECT @prev_categor := NULL, @rank := 1) i 
      CROSS 
      JOIN (SELECT s.category_id, s.user_id, SUM(s.points) AS total_points 
        FROM users s 
        GROUP BY s.category_id, s.user_id 
        ORDER BY s.category_id, total_points DESC 
       ) r 
     ORDER BY r.category_id, r.total_points DESC, r.user_id DESC 
    ) t 
    ORDER BY t.user_id, t.category_id 

ПРИМЕЧАНИЕ: У меня есть нет настроить демонстрацию скрипта SQL. Я привел пример запроса, который проверен только на столе.

Смежные вопросы