Я столкнулся с проблемой, связанной с серией сгруппированных аннотаций в Django QuerySet, который фильтруется ManyToMany. Поскольку я слишком долго смотрел на свой экран, давайте представим серию моделей о дегустациях вин:
class Event(model.Model):
# Some Fields
class Wine(models.Model):
# Some Fields
class Tasting(models.Model):
event = models.ManyToManyField(Event)
wine = models.ForeignKey(Wine)
score = models.IntegerField()
Я хочу получить некоторое агрегирование данных по вину, при желании фильтруя определенные события. С этим образцом данных дегустации (на которых я буду запускать агрегирование):
| wine_id | score | event_ids |
| ------- | ----- | --------- |
| 1 | 50 | [1] |
| 1 | 50 | [1] |
| 1 | 50 | [1, 2] |
| 2 | 100 | [1, 2] |
| 2 | 150 | [1, 2] |
| 3 | 75 | [1] |
Ожидаемый результат для приведенных выше данных:
[
{'wine_id': 1, 'total_scores': 150, 'average_scores': 50},
{'wine_id': 2, 'total_scores': 250, 'average_scores': 125},
{'wine_id': 3, 'total_scores': 75, 'average_scores': 75},
]
Попытка 1
Просто какие-то обычные values
и annotation
Tasting.objects.filter(
event__in=Event.objects.filter(id__in=[1,2])
).distinct().values('wine_id').annotate(
total_scores=Sum('score'),
average_scores=Avg('scores'),
)
Какие выходят:
[
{'wine_id': 1, 'total_scores': 200, 'average_scores': 50}, # Total score too high
{'wine_id': 2, 'total_scores': 250, 'average_scores': 125},
{'wine_id': 3, 'total_scores': 75, 'average_scores': 75},
]
Хм, так что, похоже, я сталкиваюсь с тем же самым проблема, которая возникает с множественными аннотациями - из-за объединения при фильтрации событий одна из wine_1
строк учитывается дважды: один раз для каждого события.
Попытка 2
Итак, глядя на кучу предложений из этой Django проблемы (например, этот ответ , я решил, что смогу решить проблему с помощью подзапросов, который привел меня к этому зверю:
total_subquery = Subquery(Tasting.objects.filter(wine_id=OuterRef('wine_id')).annotate(
total_scores=Sum('score'),
).values('total_scores'))
average_subquery = Subquery(Tasting.objects.filter(wine_id=OuterRef('wine_id')).annotate(
average_scores=Avg('scores'),
).values('average_scores'))
Tasting.objects.filter(
event__in=Event.objects.filter(id__in=[1,2])
).distinct().values('wine_id').annotate(
total_scores=total_subquery,
average_scores=average_subquery,
)
Итак, изначально это выглядело правильно:
[
{'wine_id': 1, 'total_scores': 150, 'average_scores': 50},
{'wine_id': 2, 'total_scores': 250, 'average_scores': 125},
{'wine_id': 3, 'total_scores': 75, 'average_scores': 75},
]
Ура! НО, что, если мы изменим фильтр, чтобы включить только событие 2:
Tasting.objects.filter(
event__in=Event.objects.filter(id__in=[2])
).distinct().values('wine_id').annotate(
total_scores=total_subquery,
average_scores=average_subquery,
)
В этом случае я все равно получаю данные для ВСЕХ событий. Это имеет интуитивный смысл, поскольку подзапросы ничего не знают о внешнем фильтре. Однако, если я изменю OuterRef
значений в подзапросе (до чего-то вроде filter(pk=OuterRef('pk'))
), то правильная группировка в подзапросах разваливается. Если я повторно добавлю фильтрацию событий на уровне подзапроса, то мы получим ту же проблему с повторяющейся строкой, что и в нашем первом
Я могу получить правильные значения, просто получив все данные и затем выполнив агрегирование в Python, но это серьезно снижает производительность для больших наборов данных. Есть ли способ сделать это агрегирование ion полностью через ORM?