Оптимизировать Django Queryset для цикла - PullRequest
0 голосов
/ 09 декабря 2018

Как я могу оптимизировать следующий набор запросов?

[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 голосов
/ 09 декабря 2018

Виллем прав, что 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 Я написал полезный для подобных ситуаций.

0 голосов
/ 09 декабря 2018

Я хочу избавиться от цикла for и попасть в базу данных только один раз.

Ну goal - это ForeignKey, так что это означает, что это традиционный N + 1 проблема, вы можете уменьшить нагрузку, используя .select_related(..) или .prefetch_related(..):

[link.goal for link in self.child_links<b>.select_related('goal')</b>.all()]
...