正确注释查询集的等级字段的正确方法 [英] Proper way to annotate a rank field for a queryset

查看:50
本文介绍了正确注释查询集的等级字段的正确方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设这样的模型:

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)

我想要一个queryset函数来返回每个人的总得分,这是他所有游戏得分和他在所有会话中花费的所有时间的总和,以及一个人相对于所有人的排名.如下所示:

I want to have a queryset function to return total score of each person which is addition of all his games score and all times he has spent in all his sessions alongside with a rank that a person has relative to all persons. Something like below:

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'))

我可以找到一种计算总分的方法,但是密集等级不是我想要的,因为它只是基于当前查询集中的人员来计算等级,但是我想计算一个人相对于所有人员的等级.

I could find a way to calculate total score, but dense rank is not what I want, because it just calculates rank based on persons in current queryset but I want to calculate rank of a person relative to all persons.

我使用django 1.11和postgres 10.5,请建议我一种在查询集中查找每个人的排名的正确方法,因为我希望能够在计算total_score和排名之前或之后添加另一个过滤器.

I use django 1.11 and postgres 10.5, please suggest me a proper way to find rank of each person in a queryset because I want to able to add another filter before or after calculating total_score and rank.

推荐答案

遗憾的是,这是不可能的操作,因为(对我而言)postgresql WHERE 操作(过滤器/排除)使之前的行变窄聚合功能可以对它们起作用.

Sadly, it is not a possible operation since (to me) the postgresql WHERE operation (filter/exclude) narrows the rows before the aggregation functions can work on them.

我发现的唯一解决方案是,用一个单独的查询集简单地计算所有 Person 的排名,然后用这些结果注释您的查询集.

The only solution I found is to simply compute the ranking for all Person with a separate queryset and then, to annotate your queryset with these results.

此答案(请参见改进的方法)说明了如何使用dict中的外部准备数据注释查询集"

This answer (see the improved method) explains how to "annotate a queryset with externally prepared data in a dict".

这是我为您的模型所做的实现:

Here is the implementation I made for your models:

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兼容的版本!

I tested it with Django 2.1.3 and Postgresql 10.5, so the code may lightly change for you.
Feel free to share a version compatible with Django 1.11!

这篇关于正确注释查询集的等级字段的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆