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

Cha*_*tha 13 django annotate django-queryset

在下面的设置中,我想要一个带有项目列表的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)
Run Code Online (Sandbox Code Playgroud)

我像这样制作我的QuerySet:

Projects.objects.annotate(tasks_duration=Sum('task__duration'), subtasks_duration=Sum('task__subtask__duration'))
Run Code Online (Sandbox Code Playgroud)

Django annotate()中解释的行为相关的多次导致错误的答案我得到的task_duration比它应该高得多.多个annotate(Sum())子句在结果SQL中产生多个左内连接.对于tasks_duration只有一个注释(Sum())项,结果是正确的.但是,我想同时拥有tasks_duration和subtasks_duration.

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

Ahm*_*mad 8

这里报告这个bug ,但即使在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'),
 )
Run Code Online (Sandbox Code Playgroud)

更新: 我发现你需要使用子查询.在下面的解决方案,首先您筛选相关任务的outerref(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')
)
Run Code Online (Sandbox Code Playgroud)

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


tBu*_*uLi 1

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

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