Django:多个分组注释,带有过滤的多个注释

2024-06-11 22:02:55 发布

您现在位置:Python中文网/ 问答频道 /正文

我遇到了一个问题,Django QuerySet上的一系列分组注释正在被许多人过滤。由于我盯着屏幕看了太久,让我们想象一下一系列关于品酒的模型:

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对数据进行一些聚合,可以选择对某些事件进行过滤。使用此示例品尝数据(我将在其上运行聚合):

| 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

只是一些普通的valuesannotation

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},
]

Hrm,所以看起来我遇到了与multiple annotations相同的问题——由于过滤事件时的连接,wine_1行中的一行被计数两次:每个事件一次

尝试2

因此,从Django问题(例如this answer)的一系列建议来看,我认为我可以用子查询来解决这个问题,这让我想到了这个问题:

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},
]

Huzzah!但是,如果我们将筛选器更改为仅包含事件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进行聚合来获得正确的值,但是这会给较大的数据集带来严重的性能代价。有没有一种方法可以完全通过ORM进行聚合


Tags: 数据ineventidobjectsmodels事件filter
1条回答
网友
1楼 · 发布于 2024-06-11 22:02:55

希望你不要太沮丧:)

在这种情况下,您需要做的是避免Events表的联接,以停止重复计数的疯狂行为

Wine.objects.filter(
    tasting__in=Tasting.event.through.objects.filter(event_id__in=[1, 2]).values('tasting_id')
).values('id').annotate(
    total_score=Sum('tasting__score'),
    average_scores=Avg('tasting__score')
)
[
{'id': 1, 'total_score': 150, 'average_scores': 50.0}, 
{'id': 2, 'total_score': 250, 'average_scores': 125.0}, 
{'id': 3, 'total_score': 75, 'average_scores': 75.0}
]

相关问题 更多 >