11

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

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_relatedannotate和来解决这个问题subquery,但我无法得到我需要的结果。

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

我确信我可以用原始 sql 做一些事情.extra(),但是文档宣布它将被弃用(我在 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
""")
4

3 回答 3

16

我认为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. 使用 .values 和 .annotate 进行分组

2. order_by() 问题

3.子查询

于 2018-07-02T21:29:02.173 回答
4

您应该能够使用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()
        )
    )
)
于 2018-06-28T07:15:14.303 回答
0

我喜欢以下方式:

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

于 2021-09-02T13:09:22.290 回答