У меня есть базовая таблица лидеров c в Django 2, которая выглядит следующим образом:
from django.db import models
class Leaderboard(models.Model):
username = models.CharField(max_length=200)
score = models.PositiveIntegerField()
class Meta:
indexes = [models.Index(fields=["-score"])]
def __str__(self):
return self.username
Я могу произвести ранжирование этой таблицы лидеров с помощью встроенных оконных функций Django. Rank()
и PercentRank()
:
from django.db.models import F, Window
from django.db.models.functions import PercentRank, Rank
entries = (
Leaderboard.objects.annotate(
rank=Window(
expression=Rank(),
order_by=F("score").desc(),
),
percentile=Window(
expression=PercentRank(),
order_by=F("score").desc(),
),
)
)
print("Rank\tPercentile\tScore\tUser ID\tUser Name")
for entry in entries:
print(f"{entry.rank}\t{entry.percentile:.3f}\t{entry.score}\t{entry.id}\t{entry.username}")
Это создает таблицу с возможностью ранжирования с рейтингом:
Rank Percentile Score User ID User Name
1 0.000 1000 564 Eager Guard
2 0.001 999 302 Top Hawk
2 0.001 999 747 Eager Whistler
2 0.001 999 842 Extreme Legend
5 0.004 997 123 Witty Guard
5 0.004 997 201 Arctic Elephant
7 0.006 996 21 Speedy Bear
7 0.006 996 678 Agile Player
9 0.008 995 562 Fast Hawk
10 0.009 994 467 Loyal Legend
Однако у меня возникают трудности при запросе указанного c идентификатора пользователя в таблице лидеров и получения их ранг и процентиль. Кажется, что результат оконных функций применяется только в пределах текущего набора запросов (ie, ограниченный фильтром для идентификатора пользователя) вместо всего набора лидеров:
entry = (
Leaderboard.objects.filter(id=100).annotate(
rank=Window(
expression=Rank(),
order_by=F("score").desc(),
),
percentile=Window(
expression=PercentRank(),
order_by=F("score").desc(),
),
)
.first()
)
Rank Percentile Score User ID User Name
1 0.000 876 100 Clear Star
Правильный ранг для этого пользователя должен быть:
Rank Percentile Score User ID User Name
135 0.134 876 100 Clear Star
Итак, вопросы:
1) Как запросить указанный c идентификатор пользователя и получить его правильный ранг / процентиль в списке лидеров?
2) Как запросить «ломтик» таблицы лидеров? Например, с учетом ID пользователя = 100, как запросить их запись и +/- 5 записей вокруг них? Так что, если они заняли 135 место в списке лидеров, как запросить 10 записей вокруг них?
Postgres - это производственная база данных, но я бы хотел, чтобы она оставалась агрегированной в СУБД c, если это возможно.
Любая помощь приветствуется! Спасибо!
РЕДАКТИРОВАТЬ:
Мне удалось ответить на мой первый вопрос (как запросить конкретную c запись в таблице лидеров по идентификатору пользователя) с использованием Raw SQL API:
SINGLE_ENTRY_SQL = """
SELECT
id,
username,
score,
rank,
percentile_rank
FROM (
SELECT
U0.id,
U0.username,
U0.score,
RANK() OVER (ORDER BY U0.score DESC) AS rank,
PERCENT_RANK() OVER (ORDER BY U0.score DESC) AS percentile_rank
FROM (
SELECT
id,
username,
score
FROM leaderboard
) AS U0
)
WHERE
id = %s
"""
# Query for leaderboard entry where User ID=100
entry = Leaderboard.objects.raw(SINGLE_ENTRY_SQL, [100])[0]
Теперь это дает правильный результат для одной записи:
Rank Percentile Score User ID User Name
135 0.134 876 100 Clear Star
Однако мне все еще трудно запросить "ломтик" таблицы лидеров с указанным c Идентификатор пользователя.