Правильный способ аннотировать поле ранга для набора запросов - PullRequest
0 голосов
/ 10 октября 2018

Предположим, что модели такие:

class Person(models.Model):
  name = models.CharField(max_length=20)

class Session(models.Model):
  start_time = models.TimeField(auto_now_add=True)
  end_time = models.TimeField(blank=True, null=True)
  person = models.ForeignKey(Person)

class GameSession(models.Model):
  game_type = models.CharField(max_length=2)
  score = models.PositiveIntegerField(default=0, blank=True)
  session = models.ForeignKey(Session)

Я хочу, чтобы у меня была функция набора запросов, которая возвращала бы общий балл каждого человека, который является суммой всех его игровых очков и времени, которое он провел во всех своих сессиях вместе сс рангом, который человек имеет по отношению ко всем людям.Примерно так:

class DenseRank(Func):
  function = 'DENSE_RANK'
  template = '%(function)s() Over(Order by %(expressions)s desc)'

class PersonQuerySet(models.query.QuerySet):
  def total_scores(self):
    return self.annotate(total_score=some_fcn_for_calculate).annotate(rank=DenseRank('total_score'))

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

Я использую django 1.11 и postgres 10.5, пожалуйста, предложите мне правильный способ найти ранг каждого человека в наборе запросов, потому что я хочу иметь возможность добавить другой фильтр до или после вычисления total_scoreи звание.

1 Ответ

0 голосов
/ 10 ноября 2018

К сожалению, это не возможная операция, поскольку (для меня) операция postgresql WHERE (фильтрация / исключение) сужает строки, прежде чем функции агрегации могут работать с ними.

Единственное решение, которое я нашелэто просто вычислить рейтинг для всех Person с отдельным набором запросов, а затем аннотировать ваш набор запросов с этими результатами.

Этот ответ (см. улучшенный метод) объясняет, как "аннотировать набор запросов с внешне подготовленными данными в формате dict ".

Вот реализация, которую я сделал для ваших моделей:

class PersonQuerySet(models.QuerySet):
    def total_scores(self):
        # compute the global ranking
        ranks = (Person.objects
                 .annotate(total_score=models.Sum('session__gamesession__score'))
                 .annotate(rank=models.Window(expression=DenseRank(),
                                              order_by=models.F('total_score').decs()))
                 .values('pk', 'rank'))
        # extract and put ranks in a dict
        rank_dict = dict((e['pk'], e['rank']) for e in ranks)

        # create `WHEN` conditions for mapping filtered Persons to their Rank
        whens = [models.When(pk=pk, then=rank) for pk, rank in rank_dict.items()]
        # build the query
        return (self.annotate(rank=models.Case(*whens, default=0,
                                               output_field=models.IntegerField()))
                .annotate(total_score=models.Sum('session__gamesession__score')))

Я протестировал его с Django 2.1.3 и Postgresql 10.5, поэтомукод может слегка измениться для вас.
Не стесняйтесь поделиться версией, совместимой с Django 1.11!

...