【问题标题】:Django reduce amount of queries (M2M relation with through model)Django 减少查询量(M2M 关系与直通模型)
【发布时间】:2023-02-17 20:08:19
【问题描述】:

我想减少类似查询的数量。这是我的模型:

class Skill(models.Model):
    name = models.TextField()

class Employee(models.Model):

    firstname = models.TextField()
    skills = models.ManyToManyField(Skill, through='SkillStatus')

    def skills_percentage(self):
        completed = 0
        total = 0

        for skill in self.skills.all().prefetch_related("skillstatus_set__employee"):
            for item in skill.skillstatus_set.all():
                if item.employee.firstname == self.firstname:
                    total += 1
                    if item.status:
                        completed += 1
        try:
            percentage = round((completed / total * 100), 2)
        except ZeroDivisionError:
            percentage = 0.0
        return f"{percentage} %"

class SkillStatus(models.Model):
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
    skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
    status = models.BooleanField(default=False)

我的主要问题与方法 skills_percentage 有关,我在计算提到的值时进行了太多查询。我已经使用 prefetch_related 稍微改善了情况,但是 Django 调试工具栏中仍然有额外的查询。这里还能做什么?

我尝试使用 select_related 和 prefetch_related 的不同组合。我考虑了其他选项来计算 skills_percentage 但它们也需要许多查询......

提前致谢。

【问题讨论】:

    标签: python-3.x django many-to-many django-queryset django-prefetch-related


    【解决方案1】:

    你可以这样尝试:

    employees = Employee.objects.annotate(
        total=Count('skills',distinct=True),
        completed = Count('skills', filter=Q(skills__status=True),distinct=True)
    ).annotate(
        percentage= Cast(
            F('completed') / F('total'),
            output_field=FloatField()
        )
    )
    

    在这里我计算技能两次,一次没有任何条件,第二次有过滤条件。然后我用casting将完成/总价值与原始查询集的划分注释为FloatField

    【讨论】:

      【解决方案2】:

      您可以使用 Django 的 ORM 提供的聚合函数。这有助于减少计算员工的已完成技能数和总技能数所需的查询次数。

      from django.db.models import Count, Sum
      
      class Employee(models.Model):
          ...
      
          def skills_percentage(self):
              completed = self.skills.filter(skillstatus__employee=self, skillstatus__status=True).aggregate(Count('id'))['id__count'] or 0
              total = self.skills.filter(skillstatus__employee=self).aggregate(Count('id'))['id__count'] or 0
              try:
                  percentage = round((completed / total * 100), 2)
              except ZeroDivisionError:
                  percentage = 0.0
              return f"{percentage} %"
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-22
        • 2020-04-13
        • 2016-05-07
        • 2016-09-19
        • 2015-07-25
        • 2012-07-28
        • 2023-03-06
        • 1970-01-01
        相关资源
        最近更新 更多