代码之家  ›  专栏  ›  技术社区  ›  Eldamir Henri Siponen

Django条件子查询聚合

  •  6
  • Eldamir Henri Siponen  · 技术社区  · 6 年前

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

    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_related ,请 prefetch_related ,请 annotate ,和 subquery 但是我没能得到我需要的结果。

    理想情况下,每个 公司 在查询集中,应使用整数注释 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
    """)
    
    2 回复  |  直到 6 年前
        1
  •  5
  •   Alexandr Tatarinov    6 年前

    Subquery

    # 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')
        )
    )
    

    生成的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"
    

    1. GROUP BY using .values and .annotate

    2. order_by() problems

    3. Subquery

        2
  •  3
  •   solarissmoke    6 年前

    你应该可以用 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()
            )
        )
    )
    
    推荐文章