Django аннотирует набор запросов по предикатам и подсчитывает результаты

У меня есть две модели:

class Game(models.Model):
    id = models.AutoField(primary_key=True)


class Score(models.Model):
    id = models.AutoField(primary_key=True)
    game = models.ForeignKey(Game, related_name="score", on_delete=models.CASCADE)
    first_score = models.IntegerField(blank=True)
    second_score = models.IntegerField(blank=True)
    is_rusk = models.BooleanField(blank=True)

И я получил набор объектов Game:

[
    {
        "id": 314317035,
        "score": [
            {
                "first_score": 5,
                "second_score": 1,
                "is_rusk": false
            }
        ]
    },
    {
        "id": 311298177,
        "score": [
            {
                "first_score": 5,
                "second_score": 2,
                "is_rusk": false
            }
        ]
    },
    {
        "id": 310278749,
        "score": [
            {
                "first_score": 5,
                "second_score": 2,
                "is_rusk": false
            }
        ]
    },
    {
        "id": 309866238,
        "score": [
            {
                "first_score": 5,
                "second_score": 0,
                "is_rusk": true
            }
        ]
    },
    {
        "id": 307926664,
        "score": [
            {
                "first_score": 5,
                "second_score": 0,
                "is_rusk": true
            }
        ]
    },
    {
        "id": 306047964,
        "score": [
            {
                "first_score": 4,
                "second_score": 5,
                "is_rusk": false
            }
        ]
    },
    {
        "id": 304881611,
        "score": [
            {
                "first_score": 5,
                "second_score": 3,
                "is_rusk": false
            }
        ]
    },
    {
        "id": 304468136,
        "score": [
            {
                "first_score": 5,
                "second_score": 2,
                "is_rusk": false
            }
        ]
    },
]

Я хочу аннотировать этот кверисет с rusks_cnt, это будет подсчет объектов с is_rusk=True, Если есть способ не добавлять это к каждому объекту, просто как одно поле, это тоже было бы хорошо.

Я думаю, что проще всего сделать это следующим образом:

cnt = queryset.filter(score__is_rusk=True).count()

Но когда я пытаюсь сделать аннотацию следующим образом:

cnt = queryset.filter(score__is_rusk=True).count()
queryset = queryset.annotate(cnt=cnt)

В нем говорится:

QuerySet.annotate() received non-expression(s): 2.

Я также пробовал:

queryset = queryset.annotate(
         rusk_cnt=Sum(
                Case(When(score__is_rusk=True, then=1)), output_field=IntegerField()
            )
        )

Но результаты таковы:

[
    {
        "id": 279658929,
        "rusk_cnt": 1
    },
    {
        "id": 279796553,
        "rusk_cnt": null
    },
    ...
]

Также мне интересно, приведет ли простое использование .count() к плохой производительности?

Вы можете аннотировать с помощью Value:

from django.db.models import Value

cnt = queryset.filter(score__is_rusk=True).count()
queryset = queryset.annotate(cnt=Value(cnt))

Но это добавит одно и то же значение: количество Scoreобъектов для Game в queryset к всех Game объектов, что не имеет особого смысла.

Если вы хотите аннотировать Game объекты с True количеством объектов, где Score с is_rusk=True, вы можете работать с:

from django.db.models import Q, Sum

queryset.annotate(
    rusk_cnt=Sum('score', filter=Q(score__is_rusk=True))
)

Annotate предназначен для вычисления по каждой записи. Если вы хотите рассчитать для всего набора запросов, используйте Aggregate.

Разница между методами annotate и aggregate в Django?

Вернуться на верх