首页 文章

优化Django Queryset for循环

提问于
浏览
1

如何优化以下查询集?

[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 回答

  • 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我写的对这种情况有帮助 .

  • 2

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

    那么 goalForeignKey ,所以这意味着这是一个传统的N 1问题,你可以通过使用 .select_related(..).prefetch_related(..) 减少负载:

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

相关问题