Эффективный SQL-запрос / схема для таблицы лидеров - PullRequest
8 голосов
/ 23 февраля 2009

Я написал маленькую глупую игру и хочу иметь какой-то сайт лидеров.

Обычно таблицы лидеров ограничены 10 или 20 топ-игроками, но я подумал, что было бы неплохо, если бы я мог записать, для каждого игрока, их лучший результат . Тогда я всегда мог показать их всемирное звание.

Простая схема, такая как:

create table leaderboard (
    userid varchar(128) not null,
    score real not null,
    when datetime not null
);
create index on leaderboard(userid);

Хранит минимальное количество необходимой мне информации - 1 запись на пользователя с лучшим счетом.

Мой вопрос вращается вокруг того, как эффективно определить чью-либо позицию в таблице лидеров. Общая идея заключается в том, что я хотел бы, чтобы их позиция в списке возвращалась:

select userid from leaderboard order by score desc

Но запуск этого запроса с последующим линейным поиском в списке кажется мне немного нелепым с точки зрения производительности БД. Несмотря на это, мне трудно представить запрос / схему, которая бы сделала его быстрой операцией.

Есть идеи?

(Я бы предпочел сохранить схему БД и общий запрос (не привязанным к поставщику). Но, если один поставщик делает это проще, я с удовольствием использую либо MS SQL, либо MySQL.

Ответы [ 7 ]

13 голосов
/ 23 февраля 2009

Как насчет:

select count(*)+1 as rank from leaderboard  
where score > (select score from leaderboard where userid = ?)

Вам также понадобится индекс для столбца оценки.

Выполнение count()+1 с score > (...) даст вам точные ранги, даже если у нескольких игроков одинаковый счет; делать count() с score >= (...) не буду.

2 голосов
/ 23 февраля 2009

В SQL Server 2005 и более поздних версиях вы можете использовать функцию RANK(), чтобы возвращать рейтинг для каждого пользователя на основе его оценки

SELECT 
    userid,
    RANK() OVER 
    (ORDER BY score) AS rank
FROM leaderboard

Если у вас было более одного типа «типа игры», вы можете включить его в таблицу лидеров и использовать условие PARTITION BY в функции RANK, чтобы определить рейтинг для каждого типа игры.

1 голос
/ 31 июля 2009

В Sql server 2005 Rank () в значительной степени делает эту работу за вас. Но если у вас есть миллионы записей, то ранжирование их в режиме реального времени при каждом изменении базовой статистики приведет к снижению производительности.

Я пытался создать индексированное представление поверх запроса select ... (проголосовавший ответ в этой теме), но Sql 2005 не позволил бы мне его создать, потому что вы не можете использовать подзапросы, самоссылку в индексированном представлении.

Таким образом, наш обходной путь - обновлять таблицу ранжирования по ночам, используя функцию Row (). Чтобы избежать блокировки при выполнении этого обновления, мы сохраняем 2 копии таблицы ранжирования, одну из которых обновляют, а другую - используемую в приложении. У нас есть RankingView, которое указывает на таблицу активного ранжирования в любой момент времени.

Хотелось бы узнать, есть ли решение для обновления ранжирования в реальном времени для действительно больших таблиц?

1 голос
/ 23 февраля 2009

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

1 голос
/ 23 февраля 2009

Похоже, вы хотите запросить это:

select userid , max(score)
from leaderboard 
group by userid
order by max(score) desc

Возвращает таблицу лидеров с 1 записью для каждого пользователя.

РЕДАКТИРОВАТЬ НИЖЕ: Я вижу в комментарии, что вы хотите видеть рейтинг, а не счет. для этого я не знаю ответ ANSI SQL, но специфичен для базы данных:

В MySQL:

SELECT @rownum:=@rownum+1 rank
, t.userid 
FROM (SELECT @rownum:=0) r, 
 (select userid , score
from leaderboard 
order by score desc
) t;

В Oracle вы можете использовать инструкцию RANK.

1 голос
/ 23 февраля 2009

Очевидным вариантом будет создание индекса по «счету», что вполне разумно. (Звучит так, как будто вы хотите сохранить два значения - совокупный балл и максимальный балл - или я не так понимаю?)

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

0 голосов
/ 27 февраля 2015

Я думал о избранных "выберите количество (*) + 1 в качестве рейтинга в таблице лидеров
где оценка> (выберите оценку в таблице лидеров, где ID пользователя =?) "

Подумайте о следующей ситуации: Игрок 1, Счет 100 Игрок 2, счет 100 Игрок 3, счет 50

При использовании этого SQL ранг игрока 3 будет 3, где правильный ответ должен быть 2, потому что игрок 1 и игрок 2 связаны в первой позиции. Агрегирующая функция count () в этом случае учитывает 2 записи с колонкой Score> 50.

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

select score, count(*)+1 as rank from leaderboard  
group by score having (score) > (select score from leaderboard where userid = ?)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...