Как построить таблицу лидеров в Django, которая позволяет произвольные запросы и нарезки? - PullRequest
0 голосов
/ 05 апреля 2020

У меня есть базовая таблица лидеров 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 Идентификатор пользователя.

...