Django:在相关mod中计算外键值的正确方法

2024-05-19 19:29:03 发布

您现在位置:Python中文网/ 问答频道 /正文

我不确定对相关模型中外键整型字段的值求和的最佳做法是什么。以django教程为基础,给出一个构建库的示例:

假设您已经创建了图书库和BookInstance,其中每本书都包含许多真实的图书实例。您在BookInstance上维护一个整数字段,其签出次数为。这样您可以看到每个特定实例签出了多少次。现在您需要合计在图书模型中签出实例的所有时间,以便可以快速参考图书馆中签出最多的图书。在图书模型中对所有这些价值进行合计的最佳做法是什么

我目前有一个有效的解决方案,它存储一个整数字段,用于在book模型本身上签出的次数,然后在book模型中有一个函数,我从BookInstance模型调用该函数:

def update_times_checked_out(self):
    instances = BookInstance.objects.filter(book__id = self.id)
    times_checked_out = 0
    for i in instances:
        times_checked_out += i.number_of_times_checked_out
    self.number_of_times_checked_out = times_checked_out
    self.save()

这目前运行良好,但我可以想象,随着越来越多的BookInstance添加到数据库中,这可能会变得非常缓慢。我也知道,如果不同的BookInstance同时调用两次,这可能会有一个竞争条件

我知道F()表达式,并在简单递增+=1的情况下使用它们,但在我的现实世界中,此更新不仅处理常量值增量,而且处理值的大跳跃(从一个实例开始,随时可能是+1/10/500)。因此,我不知道如何正确地使用F()表达式来处理这个问题

有没有更好的方法来得到我现在得到的同样的结果


Tags: instances实例函数模型selfidnumber数字
1条回答
网友
1楼 · 发布于 2024-05-19 19:29:03

更新单个对象

您可以使用^{} [Django-doc]来实现这一点:

from django.db.models import Sum

def update_times_checked_out(self):
    self.number_of_times_checked_out = BookInstance.objects.filter(
        book__id = self.id
    ).aggregate(
        checked_out=Sum('number_of_times_checked_out')
    )['checked_out']
    self.save()

或者如果当前模型的BookInstance外键的related_name不变,我们甚至可以使用:

from django.db.models import F, Sum

def update_times_checked_out(self):
    self.number_of_times_checked_out = self.book_instance_set.aggregate(
        checked_out=Sum('number_of_times_checked_out')
    )['checked_out']
    self.save()

因此,我们在这里将相关元素的number_of_times_checked_out汇总在一个查询中,并将其存储在number_of_times_checked_out

批量更新

如果您想一次更新此模型的所有number_of_times_checked_out,我们可以使用子查询进行查询以批量执行更新:

from django.db.models import OuterRef, Subquery, Sum

MyModel.objects.update(
    number_of_times_checked_out=Subquery(
        BookInstance.objects.filter(
            book__id=OuterRef('id')
        ).annotate(
            checked_out=Sum('number_of_times_checked_out')
        ).values('checked_out')[:1])
)

在这里,所有MyModel对象都将在单个查询中更新number_of_times_checked_out字段。当然,该查询将比单个对象的查询花费更多,但通常比单独使用上述方法更新每个对象快几倍

@BradMartsberger所说,您也可以使用django-sql-utils包,然后使用SubquerySum,如:

from sql_util.utils import SubquerySum

MyModel.objects.update(
    number_of_times_checked_out=SubquerySum('bookinstance__checked_out')
)

相关问题 更多 >