python-3.x Django减少了查询量(M2M关系通过模型)

mwecs4sa  于 2023-02-20  发布在  Python
关注(0)|答案(2)|浏览(152)

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

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,但它们也需要许多查询。
先谢了。

luaexgnf

luaexgnf1#

你可以这样尝试:

from django.db.models import Count, When, Case, Cast, FloatField

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

# usage

for employee in employees:
    print(employee.percentage)

# or 

employees.values('firstname', 'percentage')

这里我计算了两次技能,第一次没有total条件,第二次使用了过滤条件,然后我用原始的queryset注解了completed/total值的除法,将其转换为FloatField

erhoui1w

erhoui1w2#

可以使用Django的ORM提供的聚合函数,这有助于减少计算员工已完成技能和总技能计数所需的查询数量。
使用F()表达式和annotate()允许我们在单个查询中执行计算,而不需要在相关的Skill对象上进行单独的循环。

from django.db.models import Count, F

class Employee(models.Model):
    ...

    def skills_percentage(self):
        counts = self.skills.annotate(
            completed_count=Count('skillstatus', filter=Q(skillstatus__status=True, skillstatus__employee=self)),
            total_count=Count('skillstatus', filter=Q(skillstatus__employee=self)),
        ).aggregate(
            completed_count_sum=Sum('completed_count'),
            total_count_sum=Sum('total_count'),
        )

        completed = counts['completed_count_sum'] or 0
        total = counts['total_count_sum'] or 0
        try:
            percentage = round((completed / total * 100), 2)
        except ZeroDivisionError:
            percentage = 0.0
        return f"{percentage} %"

相关问题