避免每次for循环迭代都进入数据库

时间:2017-06-09 15:20:29

标签: django

我想将数据导出到Django中的Excel文件。首先,我得到所有成员:

members_list = Member.objects\
            .exclude(deleted=True) \
            .annotate(num_calculations=Count('user__calculations'))\
            .order_by("-user__date_joined")

然后我遍历成员并将它们添加到excel文件中:

for row in members_list:
            row_num += 1
            ws.write(row_num, 0, row.number or "", font_style)
            ws.write(row_num, 1, row.user.first_name or "", font_style)
            ws.write(row_num, 2, row.user.last_name or "", font_style)
            ws.write(row_num, 3, row.address or "", font_style)
            col_num = 3
            for calculation in calculations_per_month_last_12_months(row.number):
                col_num += 1
                ws.write(row_num, col_num, calculation['total_calculations'] or "", font_style)

函数calculations_per_month_last_12_months如下:

items = list(Calculations.objects
                 .filter(user__member__number=member_number)
                 .filter(price_date__gte=datetime.datetime.now().today() - relativedelta(months=12))
                 .annotate(date=TruncMonth('price_date'))
                 .values('date')
                 .annotate(total=Count('id'))
                 .values('date', 'total')
                 .order_by('date'))

    result = []
    for month in range(12):
        date = timezone.now() - relativedelta(months=month)

        month_results = list(filter(lambda i: date <= i['date'] + relativedelta(months=1) < (date + relativedelta(months=1)), items))

        month_result = 0

        if month_results:
            month_result = month_results[0]['total']

        result.append({
            'total_calculations': month_result
        })

    return result

一切正常,但问题是我是否有大量成员。

calculations_per_month_last_12_months函数会转到每个成员的数据库。这就是我不想要的。

如果我有5000个成员,那么它将进入数据库5000次。

任何建议我怎么能避免它?

1 个答案:

答案 0 :(得分:0)

您可以使用prefetch_related获取原始查询中的所有计算。它本质上执行SQL连接,以便它们都成为一个查询。我还添加了select_related('user')以避免对成员的用户名称进行额外查询。

members_list = Member.objects\
        .exclude(deleted=True) \
        .annotate(num_calculations=Count('user__calculations'))\
        .order_by("-user__date_joined")\
        .select_related("user")\
        .prefetch_related("user__calculations")

然后在您的函数中,您可以传入member对象并且它已经预先加载了计算,因此您可以跳过其他查询

def calculations_per_month_last_12_months(member):
    for month in range(12):
        date = timezone.now() - relativedelta(months=month)
        next_month = date + relativedelta(months=1)

        month_results = list(
            filter(
                lambda i: date <= i['date'] + relativedelta(months=1) < next_month,
                member.user.calculations
            )
        )

        month_result = 0

        if month_results:
            month_result = month_results[0]['total']

        yield {'total_calculations': month_result}

编辑:还添加了yield语法,以避免为结果列表分配额外空间。