多个注释和术语产生膨胀的答案

时间:2012-08-24 11:39:17

标签: django django-queryset annotate

在下面的设置中,我想要一个带有项目列表的QuerySet,每个项目都注释了所有任务持续时间(作为tasks_duration)的总和以及所有任务的子任务持续时间的总和(asasasks_duration)。我的模型(简化)看起来像这样:

class Project(models.Model):
    pass

class Task(models.Model):
    project = models.ForeignKey(Project)
    duration = models.IntegerField(blank=True, null=True)

class SubTask(models.Model):
    task = models.ForeignKey(Task)
    duration = models.IntegerField(blank=True, null=True)

我像这样制作我的QuerySet:

Projects.objects.annotate(tasks_duration=Sum('task__duration'), subtasks_duration=Sum('task__subtask__duration'))

Django annotate() multiple times causes wrong answers中解释的行为相关,我得到的task_duration比它应该高得多。多个annotate(Sum())子句在结果SQL中产生多个左内连接。对于tasks_duration只有一个注释(Sum())项,结果是正确的。但是,我想同时拥有tasks_duration和subtasks_duration。

进行此查询的合适方法是什么?我有一个工作解决方案,按项目执行,但预计会非常缓慢。我也有类似的工作与extra()调用,但我真的想知道我想要的纯Django是否可能。

2 个答案:

答案 0 :(得分:6)

报告错误here,但即使在Django 1.11中也没有解决。该问题与以反向关系连接两个表有关。 请注意,distinct参数适用于Count但不适用于Sum。所以你可以使用一个技巧并编写如下的ORM:

 Projects.objects.annotate(
      temp_tasks_duration=Sum('task__duration'),
      temp_subtasks_duration=Sum('task__subtask__duration'),
      tasks_count=Count('task'),
      tasks_count_distinct=Count('task', distinct=True),
      task_subtasks_count=Count('task__subtask'),
      task_subtasks_count_distinct=Count('task__subtask', distinct=True),
 ).annotate(
      tasks_duration=F('temp_tasks_duration')*F('tasks_count_distinct')/F('tasks_count'),
      subtasks_duration=F('temp_subtasks_duration')*F('subtasks_count_distinct')/F('subtasks_count'),
 )

<强>更新 我发现你需要使用Subquery。在下面的解决方案中,首先过滤与outerref相关的任务(外部查询的OuterRef引用,以便为每个项目过滤任务),然后按“项目”对任务进行分组,以便Sum适用于每个项目的所有任务,如果项目存在任何任务,则只返回一个结果(您已经过&#39;项目&#39;然后按相同的字段分组;这就是为什么只有一个团体可以在那里。)或否则无。如果项目没有任务,结果将为None,这意味着我们不能使用[0]来选择计算的总和。

from django.db.models import Subquery, OuterRef
Projects.objects.annotate(
    tasks_duration=Subquery(
        Task.objects.filter(
            project=OuterRef('pk')
        ).values(
            'project'
        ).annotate(
            the_sum=Sum('task__duration'),
        ).values('the_sum')[:1]
    ),
    subtasks_duration=Sum('task__subtask__duration')
)

运行此代码只会向数据库发送一个查询,因此性能非常好。

答案 1 :(得分:1)

我也得到了这个错误。完全相同的代码。如果我单独进行聚合,它可以工作,但是一旦我尝试同时获得两个总和,其中一个得到的因子高2,另一个得到因子3。

我不知道为什么Django会这样做。我在这里提交了一份错误报告: https://code.djangoproject.com/ticket/19011 您可能也有兴趣关注它。