【问题标题】:Django conditional Subquery aggregateDjango 条件子查询聚合
【发布时间】:2018-12-04 03:36:56
【问题描述】:

我的模型结构的简化示例是

class Corporation(models.Model):
    ...

class Division(models.Model):
    corporation = models.ForeignKey(Corporation)

class Department(models.Model):
    division = models.ForeignKey(Division)
    type = models.IntegerField()

现在我想显示一个显示公司的表格,其中一列将包含某种类型的部门数量,例如。 type=10。目前,这是通过 Corporation 模型上的帮助器实现的,该模型检索那些,例如

class Corporation(models.Model):
    ...
    def get_departments_type_10(self):
        return (
            Department.objects
            .filter(division__corporation=self, type=10)
            .count()
        )

这里的问题是,由于 N+1 问题,这绝对会破坏性能。

我尝试使用select_relatedprefetch_relatedannotatesubquery 来解决此问题,但我无法获得所需的结果。

理想情况下,查询集中的每个Corporation 都应使用整数type_10_count 进行注释,该整数反映了该类型部门的数量。

我确信我可以在 .extra() 中使用原始 sql 做一些事情,但文档宣布它将被弃用(我在 Django 1.11 上)

编辑:原始 sql 解决方案示例

corps = Corporation.objects.raw("""
SELECT
*,
(
    SELECT COUNT(*)
    FROM foo_division div ON div.corporation_id = c.id
    JOIN foo_department dept ON dept.division_id = div.id
    WHERE dept.type = 10
) as type_10_count
FROM foo_corporation c
""")

【问题讨论】:

    标签: django django-annotate django-subquery


    【解决方案1】:

    我认为使用Subquery,我们可以使用此代码获得与您提供的 SQL 类似的 SQL

    # Get amount of departments with GROUP BY division__corporation [1]
    # .order_by() will remove any ordering so we won't get additional GROUP BY columns [2]
    departments = Department.objects.filter(type=10).values(
        'division__corporation'
    ).annotate(count=Count('id')).order_by()
    
    # Attach departments as Subquery to Corporation by Corporation.id.
    # Departments are already grouped by division__corporation
    # so .values('count') will always return single row with single column - count [3]
    departments_subquery = departments.filter(division__corporation=OuterRef('id'))
    corporations = Corporation.objects.annotate(
        departments_of_type_10=Subquery(
            departments_subquery.values('count'), output_field=IntegerField()
        )
    )
    

    生成的SQL是

    SELECT "corporation"."id", ... (other fields) ...,
      (
        SELECT COUNT("division"."id") AS "count"
        FROM "department"
        INNER JOIN "division" ON ("department"."division_id" = "division"."id") 
        WHERE (
          "department"."type" = 10 AND
          "division"."corporation_id" = ("corporation"."id")
        ) GROUP BY "division"."corporation_id"
      ) AS "departments_of_type_10"
    FROM "corporation"
    

    这里的一些问题是子查询对于大型表可能会很慢。但是,数据库查询优化器可以足够聪明地将子查询提升为 OUTER JOIN,至少我听说 PostgreSQL 会这样做。

    1. GROUP BY using .values and .annotate

    2. order_by() problems

    3. Subquery

    【讨论】:

    • 一个很好的答案。还有一个很好的例子,声称 ORM 是一个泄漏的抽象。优雅当然到此结束,因为原始 SQL 变得更具可读性。 ORM 仍然具有可组合性的优点,这将是该解决方案的主要论据。谢谢
    • @Eldamir 谢谢。是的,这是可悲的事实。在某些时候,我不得不为 Django ORM 掌握如此复杂的东西,只是因为我需要它们是可组合的,以及所有的表别名等等。我会说像 SQLAlchemy 这样的东西应该不难表达并且仍然获得所有 ORM 的好处。那是 Django 不是最好的 ORM 设计,它在比 WHERE 或简单的无条件的 INNER/LOUTER JOIN 更复杂的事情上完全失败。
    【解决方案2】:

    您应该能够使用Case() 表达式来查询具有您要查找的类型的部门的数量:

    from django.db.models import Case, IntegerField, Sum, When, Value
    
    Corporation.objects.annotate(
        type_10_count=Sum(
            Case(
                When(division__department__type=10, then=Value(1)),
                default=Value(0),
                output_field=IntegerField()
            )
        )
    )
    

    【讨论】:

      【解决方案3】:

      我喜欢下面的做法:

      departments = Department.objects.filter(
          type=10,
          division__corporation=OuterRef('id')
      ).annotate(
          count=Func('id', 'Count')
      ).values('count').order_by()
      
      corporations = Corporation.objects.annotate(
          departments_of_type_10=Subquery(depatments)
      )
      

      您可以在此答案中看到有关此方法的更多详细信息:https://stackoverflow.com/a/69020732/10567223

      【讨论】:

        猜你喜欢
        • 2020-05-05
        • 2017-07-21
        • 2020-05-23
        • 1970-01-01
        • 2012-04-01
        • 1970-01-01
        • 2021-12-02
        • 2010-11-01
        • 1970-01-01
        相关资源
        最近更新 更多