【问题标题】:multiple annotate Sum terms yields inflated answer多个注释总和项会产生夸大的答案
【发布时间】:2012-08-19 23:27:09
【问题描述】:

在以下设置中,我想要一个带有项目列表的 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)

我的查询集是这样的:

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

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

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

【问题讨论】:

  • 您是否尝试过tasks_duration=Sum('task__duration', distinct=True), subtasks_duration=Sum('task__subtask__duration', distinct=True),正如您链接的另一个问题中提到的那样?
  • 这只会用于汇总不同的持续时间值,这不是我想要的。出于好奇,我确实尝试过,仍然产生相同的错误值。 (持续时间会有所不同)
  • 找到解决方法了吗?我正在尝试在一个查询集上注释一个总和和一个计数,并且总和不断增加......
  • 恐怕我从来没有让这个特殊的结构工作。我刚刚检查了上面的简单示例的派生代码:我最终在 extra() 子句中进行了计数。
  • 在 Django 1.11 中提供的 ORM Subquery 构造。当我在 2012 年发布我的问题时,Django 1.11 还遥遥无期!

标签: django django-queryset annotate


【解决方案1】:

该错误已报告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'),
 )

更新: 我发现您需要使用子查询。在以下解决方案中,首先过滤与外部引用相关的任务(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')
)

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

【讨论】:

  • 感谢您回到这里更新您的答案。它看起来不错,我想选择它作为答案,但我目前无法对其进行测试。您是否有一个包含几个示例项目 -> 任务 -> 子任务的测试设置,可以显示所有时间的正确计数?理想情况下,Django 生成的 SQL 也很容易在答案中看到,然后其他读者知道会发生什么。
  • 不客气,亲爱的@CharlBotha。是的,我的笔记本电脑里有这个,但我不确定如何将它呈现给您。有没有类似 Fiddle for python 的东西?
  • 你不能检查我的答案吗? @CharlBotha
  • @Ahmad - 我已经为类似的问题苦苦挣扎了几个小时,这对我帮助很大。谢谢。
【解决方案2】:

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

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

【讨论】:

  • 这并不是 Django 中的真正错误,而是对数据库进行这些聚合查询的复杂性问题。即使你自己写了SQL,你也必须解决这个问题。
  • 这个bug还没有修复。
  • 仍未修复,但错误报告中有一个有趣的 RawSQL 解决方案:code.djangoproject.com/ticket/10060#comment:57
  • 您不需要 RawSQL。请阅读我的更新答案。 @DavidK。
猜你喜欢
  • 2019-09-13
  • 1970-01-01
  • 2018-04-13
  • 1970-01-01
  • 2018-06-28
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多