Объединение Django по QuerySet?

Вопрос:

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

До сих пор мне удалось сделать это с кодом, подобным этому:

class Player(models.Model):
name = models.CharField(max_length=100)


class Season(models.Model):
name = models.CharField(max_length=100)
start_date = models.DateField()
end_date = models.DateField()
players = models.ManyToManyField(Player)

def get_player_rank(self, player):
return player.matchresult_set.filter(season=self).aggregate(points=Sum('points'))['points']

def get_ranks(self):
ranks = [(player, self.get_player_rank(player)) for player in self.players.all()]
ranks.sort(key=lambda tup: tup[1])
return ranks


class Match(models.Model):
date = models.DateField()
players = models.ManyToManyField(Player, through='MatchResult')
season = models.ForeignKey(Season)


class MatchResult(models.Model):
player = models.ForeignKey(Player)
match = models.ForeignKey(Match)
points = models.IntegerField()

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

Я пробовал это, но он просто суммировал все моменты в течение всего сезона:

class Season(models.Model):
def get_ranks(self):
return self.players.annotate(points=Sum('matchresult__points')).order_by('-points')

Что мне не хватает? Я думаю, что.extra() можно использовать, если это приведет к переносу кода.

Лучший ответ:

Это возвращает полезные результаты:

Season.objects.values('name','match__matchresult__player__username').annotate(points=Sum('match__matchresult__points')).distinct()

Мне также необходимо было реализовать SumWithDefault, чтобы избавиться от NULL:

from django.db.models.sql.aggregates import Aggregate
from django.db.models import Aggregate as Ag

class SumWithDefaultSQL(Aggregate):
    def __init__(self, col, default=None, **extra):
        super(SumWithDefaultSQL, self).__init__(col, default=default, **extra)
        self.sql_function = 'SUM'
        if default is not None:
            self.sql_template = 'COALESCE(%(function)s(%(field)s), %(default)s)'


class SumWithDefault(Ag):
    name = 'Sum'

    def add_to_query(self, query, alias, col, source, is_summary):
        aggregate = SumWithDefaultSQL(col, source=source, is_summary=is_summary, **self.extra)
        query.aggregates[alias] = aggregate

окончательный запрос:

Season.objects.values('name','match__matchresult__player__username').annotate(points=SumWithDefault('match__matchresult__points', default=0)).distinct().order_by('-points')

Ответ №1

Django ORM не распространяется на все варианты использования.

Вы можете хранить агрегаты в отдельной модели или вернуться к необработанному SQL.

Оцените статью
TechArks.Ru
Добавить комментарий