假设这样的模型:
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函数来返回每个人的总得分,这是他所有游戏得分和他在所有会话中花费的所有时间的总和,以及一个人相对于所有人的排名。如下所示:
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和排名之前或之后添加另一个过滤器。
答案 0 :(得分:1)
不幸的是,这是不可能的操作,因为(对我来说)PostgreSQL WHERE
操作(过滤器/排除)使行变窄,然后聚合函数才能对其进行操作。
我发现的唯一解决方案是简单地使用单独的查询集计算所有Person
的排名,然后用这些结果注释您的查询集。
This answer(请参阅改进的方法)介绍了如何“使用字典中的外部准备数据注释查询集”。
这是我为您的模型所做的实现:
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兼容的版本!