19

在以下设置中,我想要一个带有项目列表的 QuerySet,每个项目都用其所有任务持续时间的总和(作为 tasks_duration)和所有任务的子任务持续时间的总和(作为 subtasks_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()中解释的行为相关,多次导致错误答案我得到的 tasks_duration 远高于应有的值。多个 annotate(Sum()) 子句在结果 SQL 中产生多个左内连接。tasks_duration 只有一个 annotate(Sum()) 项,结果是正确的。但是,我想同时拥有 tasks_duration 和 subtasks_duration。

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

4

2 回答 2

14

此处报告了该错误,但即使在 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'),
 )

更新: 我发现您需要使用子查询。在以下解决方案中,首先过滤与外部查询相关的任务(OuterRef 引用外部查询,因此针对每个项目过滤任务),然后按“项目”对任务进行分组,以便总和适用于所有每个项目的任务,如果项目存在任何任务,则仅返回一个结果(您已按“项目”过滤,然后按同一字段分组;这就是为什么只有一组可以在那里。)否则没有。如果项目没有任务,结果将为 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')
)

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

于 2017-08-21T18:08:29.127 回答
1

我也收到此错误。完全相同的代码。如果我单独进行聚合,它会起作用,但是一旦我尝试同时获得两个总和,其中一个会高出 2 倍,而另一个会高出 3 倍。

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

于 2012-09-22T10:04:47.890 回答