0

实际上,我在循环中有多个查询,但我对此并不满意。我想知道是否有人具有prefetch_related其他 Django 查询构造优化方面的专业知识,能够在这个问题上为我提供帮助。

我开始:

users = User.objects.filter(organisation=organisation).filter(is_active=True)

然后,我从某个日期“ start_date”开始,在所有日子里开始循环:

for date in (start_date + datetime.timedelta(n) for n in range((datetime.datetime.now().replace(tzinfo=pytz.UTC) - start_date).days + 1)):

然后我在这个循环中遍历上面的过滤子集users

for date in (start_date + datetime.timedelta(n) for n in range((datetime.datetime.now().replace(tzinfo=pytz.UTC) - start_date).days + 1)):
  for user in users.filter(created_date__lte=date).iterator():

好的,首先,有没有办法优化这个?

是什么让一些铁杆 Django 人失去了他们的束缚,我在另一个循环中完成了上述所有操作!

for survey in Survey.objects.all().iterator():
   for date in (start_date + datetime.timedelta(n) for n in range((datetime.datetime.now().replace(tzinfo=pytz.UTC) - start_date).days + 1)):
      for user in users.filter(created_date__lte=date).iterator():

在最后一个循环中,我执行了最后一个查询过滤器:

survey_result = SurveyResult.objects.filter(survey=survey, user=user, created_date__lte=date).order_by('-updated_date')[0]

我这样做是因为我觉得我需要准备好过滤用户、调查和日期变量......

我已经开始考虑 prefetch_related 和 Prefetch 对象。我已经查阅了文档,但我似乎无法理解如何将其应用于我的情况。

实际上,查询花费的时间太长了。对于平均 1000 名用户、4 次调查和大约 30 天,此查询需要 1 分钟才能完成。

理想情况下,我想剃掉 50%。再好不过了,我会非常高兴。我还希望减少数据库服务器上的负载,因为此查询可能在不同的组织中运行多次。

任何有关如何在循环内的循环内组织此类可怕查询的专家提示将不胜感激!

完整的“浓缩”最小可行片段:


users = User.objects.filter(organisation=organisation).filter(is_active=True)

datasets = []

for survey in Survey.objects.all():
    data = []
    for date in (start_date + datetime.timedelta(n) for n in range((datetime.datetime.now().replace(tzinfo=pytz.UTC) - start_date).days + 1)):
        total_score = 0
        participants = 0

        for user in users.filter(created_date__lte=date):
             participants += 1
             survey_result = SurveyResult.objects.filter(survey=survey, user=user, created_date__lte=date).order_by('-updated_date')[0]
             total_score += survey_result.score

        # An average is calculated from the total_score and participants and append to the data array.:
        # Divide catches divide by zero errors.
        # Round will round to two decimal places for front end readability.
        data.append(
            round(
                divide(total_score, participants), 2
            )
        )

    datasets.append(data)

********* 附录:*********

因此,除了@dirkgroten 的回答,我目前正在使用:

for survey in Survey.objects.all():

                results = SurveyResult.objects.filter(
                    user__in=users, survey=survey, created_date__range=date_range
                ).values(
                    'survey',
                    'created_date',
                ).annotate(
                    total_score=Sum('normalized_score'),
                    participants=Count('user'),
                    average_score=Avg('normalized_score'),
                ).order_by(
                    'created_date'
                )

                for result in results:
                    print(result)

因为我(“认为我”)需要对每个 QuerySet 进行调查细分。

我还有其他可用的优化吗?

4

1 回答 1

1

您实际上可以组合查询并直接在查询中执行计算:

from django.db.models import Sum, Count, Avg
from django.utils import timezone
users = User.objects.filter(organisation=organisation).filter(is_active=True)
date_range = [start_date, timezone.now().date]  # or adapt end time to different time zone
results = SurveyResult.objects.filter(user__in=users, created_date__range=date_range)\
              .values('survey', 'created_date')\
              .annotate(total_score=Sum('score'), participants=Count('pk'))
              .order_by('survey', 'created_date')

这将按 and 对结果进行分组,survey并将andcreated_date添加到每个结果中,例如:total_scoreparticipants

[{'survey': 1, 'created_date': '2019-08-05', 'total_score': 54, 'participants': 20}, 
 {'survey': 1, ... } ... ]

我假设SurveyResult每个用户只有一个,所以SurveyResult每个组中的数量就是参与者的数量。

请注意,这Avg也会一次为您提供平均分数,即假设每个用户只有一个可能的分数:

.annotate(average_score=Avg('score'))  # instead of total and participants

这应该可以节省 99.9% 的查询时间 :-)

如果您希望数据集作为列表列表,您只需执行以下操作:

dataset = []
data = []
current_survey = None
current_date = start_date
for result in results
    if not result['survey'] == current_survey:
        # results ordered by survey, so if it changes, reset data
        if data: dataset.append(data)            
        data = []
        current_survey = result['survey']
    if not result['created_date'] == current_date:
        # results ordered by date so missing date won't be there later 
        # assume a daterange function to create a list of dates
        for date in daterange(current_date, result['created_date']):
            data.append(0)  # padding data
     current_date = result['created_date']
     data.append(result['average_score'])

结果将是一个列表列表:

dataset = [[0, 0, 10.4, 3.9, 0], [20.2, 3.5, ...], ...]

不是超级高效的 p​​ython,但是有几个 1000 个结果,无论如何这将是超级快的,比执行更多的数据库查询要快得多。

编辑:由于 created_date 是DateTimeField,您首先需要获取相应的日期:

from django.db.models.functions import TruncDate
results = SurveyResult.objects.filter(user__in=users, created_date__range=date_range)
               .annotate(date=TruncDate('created_date'))
               .values('survey', 'date')
               .annotate(average_score=Avg('score'))
               .order_by('survey', 'date')
于 2019-08-06T13:52:57.270 回答