优化Django Queryset进行循环

时间:2018-12-09 15:25:41

标签: python django django-models django-queryset

如何优化以下查询集?

[link.goal for link in self.child_links.all()]

我想摆脱for循环,只打一次数据库。

我有以下代码:

class Goal(models.Model):
    name = models.CharField(max_length=300)
    progress = models.SmallIntegerField(default=0)

def __str__(self):
    return self.name

def calc_progress(self):
    progress = 0
    subgoals = [link.goal for link in self.child_links.all()]
    for subgoal in subgoals:
        progress += subgoal.weight * subgoal.progress
        weight += subgoal.weight
    progress = progress / weight / len(subgoals)
    self.progress = int(progress)


class Link(models.Model):
    parent_goal = models.ForeignKey(Goal, on_delete=models.CASCADE, related_name="child_links")
    goal = models.ForeignKey(Goal, on_delete=models.CASCADE, related_name="parent_links")
    weight = models.SmallIntegerField(default=1)

def __str__(self):
    return str(self.parent_goal) + "-->" + str(self.goal)

2 个答案:

答案 0 :(得分:2)

  

我想摆脱for循环,只打一次数据库。

嗯,goal是一个ForeignKey,这意味着这是一个传统的 N + 1 问题,您可以使用{{1} }或.select_related(..)

.prefetch_related(..)

答案 1 :(得分:2)

Willem是正确的,select_related()会减少数据库查询,但是您真正应该做的是使用Django聚合将计算结果移动到数据库中。

from django.db.models import Count, F, Sum


def calc_progress(self):
    agg = (
        self.child_links
        .order_by()
        .annotate(
            progress=F('goal__weight') * F('goal__progress')
        )
        .aggregate(
            progress_sum=Sum('progress'),
            weight_sum=Sum('goal__weight'),
            count=Count('id'),
        )
    )
    progress = agg['progress_sum'] / agg['weight_sum'] / agg['count']
    self.progress = int(progress)

这是未经测试的,因此可能需要进行一些调整,但是一般的想法是,如果在数据库中完成这些计算将更加高效,并且select_related()将不再是必需的。此功能仅进行一个数据库查询。

您可能会发现我写的Django ORM Optimization cheat sheet对于这种情况很有帮助。