Вам нужна хранимая процедура, чтобы иметь возможность вызывать ее с параметрами:
CREATE TABLE rank (name VARCHAR(20) NOT NULL, points INTEGER NOT NULL);
CREATE INDEX ix_rank_points ON rank(points, name);
CREATE PROCEDURE prc_ranks(fromrank INT, tillrank INT)
BEGIN
SET @fromrank = fromrank;
SET @tillrank = tillrank;
PREPARE STMT FROM
'
SELECT rn, rank, name, points
FROM (
SELECT CASE WHEN @cp = points THEN @rank ELSE @rank := @rn + 1 END AS rank,
@rn := @rn + 1 AS rn,
@cp := points,
r.*
FROM (
SELECT @cp := -1, @rn := 0, @rank = 1
) var,
(
SELECT *
FROM rank
FORCE INDEX (ix_rank_points)
ORDER BY
points DESC, name DESC
LIMIT ?
) r
) o
WHERE rn >= ?
';
EXECUTE STMT USING @tillrank, @fromrank;
END;
CALL prc_ranks (2, 5);
Если вы создадите индекс и заставите MySQL
использовать его (как в моем запросе), то сложность запроса вообще не будет зависеть от количества строк, она будет зависеть только от tillrank
.
На самом деле он будет принимать последние tillrank
значения из индекса, выполнять некоторые простые вычисления на них и отфильтровывать первые fromrank
значения.
Время этой операции, как вы можете видеть, зависит только от tillrank
, оно не зависит от количества записей.
Я только что зарегистрировался на 400,000
строках, он выбирает ранги от 5
до 100
за 0,004
секунд (то есть мгновенно)
Важно: это работает, только если вы сортируете имена в порядке DESCENDING
. MySQL
не поддерживает предложение DESC
в индексах, это означает, что points
и name
должны быть отсортированы в одном порядке, чтобы можно было использовать INDEX SORT
(оба ASCENDING
или оба DESCENDING
) , Если вам нужна быстрая ASC
сортировка по name
, вам нужно будет сохранить отрицательных точек в базе данных и изменить знак в предложении SELECT
.
Вы также можете удалить name
из индекса и выполнить окончательный ORDER
без использования индекса:
CREATE INDEX ix_rank_points ON rank(points);
CREATE PROCEDURE prc_ranks(fromrank INT, tillrank INT)
BEGIN
SET @fromrank = fromrank;
SET @tillrank = tillrank;
PREPARE STMT FROM
'
SELECT rn, rank, name, points
FROM (
SELECT CASE WHEN @cp = points THEN @rank ELSE @rank := @rn + 1 END AS rank,
@rn := @rn + 1 AS rn,
@cp := points,
r.*
FROM (
SELECT @cp := -1, @rn := 0, @rank = 1
) var,
(
SELECT *
FROM rank
FORCE INDEX (ix_rank_points)
ORDER BY
points DESC
LIMIT ?
) r
) o
WHERE rn >= ?
ORDER BY rank, name
';
EXECUTE STMT USING @tillrank, @fromrank;
END;
Это повлияет на производительность на больших диапазонах, но вы вряд ли заметите это на малых диапазонах.