У аннотации Django есть побочный эффект на другую аннотацию - PullRequest
1 голос
/ 11 июня 2019

Я столкнулся с проблемой, когда добавление аннотации к QuerySet изменяет результаты предыдущих аннотаций.

Вот (упрощенная) настройка:

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

class Unit(models.Model):
    player = models.ForeignKey(Player, on_delete=models.CASCADE,
                               related_name='unit_set')

    rarity = models.IntegerField()

class Gear(models.Model):
    pass

class UnitGear(models.Model):
    unit = models.ForeignKey(Unit, on_delete=models.CASCADE,
                             related_name='ug_set')
    gear = models.ForeignKey(Gear, on_delete=models.PROTECT)

Хорошо работает аннотирование игроков с количеством редких 7 юнитов:

Player.objects.annotate(
    rarity7_count=Count(unit_set__rarity=7)
).values_list('name', 'rarity7_count')

[('Player1', 170),
 ('Player2', 172),
 ('Player3', 164),
 ...,
)]

Значения, возвращенные для rarity7_count выше, верны.

Если я добавлю следующую дополнительную аннотацию, это уже не так:

Player.objects.annotate(
    rarity7_count=Count(unit_set__rarity=7),
    gear_count=Count(unit_set__ug_set)
).values_list('name', 'rarity7_count', 'gear_count')

[('Player1', 476, 456),
 ('Player2', 490, 466),
 ('Player3', 422, 433),
 ...,
)]

Обратите внимание, как изменились значения rarity7_count - эти значения больше не верны! Однако значения для gear_count являются правильными.

Почему это? Как заставить обе аннотации работать, не мешая друг другу? Я перепробовал все виды вещей и в настоящее время не знаю, как это сделать.

1 Ответ

2 голосов
/ 11 июня 2019

Да , поскольку теперь существует два JOIN с, а поскольку Count(..) [Django-doc] - это количество строк, таким образом, он будет действовать как некий множитель.

Однако мы можем решить эту проблему, указав distinct=True, например:

Player.objects.annotate(
    rarity7_count=Count('unit_set'<b>, distinct=True</b>, filter=Q(unit_set__rarity=7)),
    gear_count=Count('unit_set__ug_set')
).values_list('name', 'rarity7_count', 'gear_count')

Обратите внимание, что если вы хотите, чтобы gear_count также фильтровал редкость, вам нужно снова указать filter=.

...