我不确定在相关模型中将外键整数字段的值相加的最佳实践是什么。举一个基于django教程构建库的示例:
假设您已经创建了书籍和书实例库,每本书都包含图书馆中许多真实的书本实例。您在BookInstance上维护了签出次数的integerfield。这样,您可以查看每个特定实例已签出多少次。现在,您想要总计在书籍模型中签出实例的所有时间,以便您可以快速参考图书馆中已签出最多的书籍。在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()
这目前可以正常工作,但是我可以想象,随着越来越多的BookInstances添加到数据库中,这可能会开始变得非常缓慢。我也知道,如果不同的BookInstance同时调用两次,可能会出现竞争状态。
我了解F()表达式,并在++ 1情况的简单增量中使用它们,但在我的实际情况下,此更新不仅会处理常量值增量,而且还会处理值的大跃迁(可能为+1/10 / 500,可随时从1个实例开始)。因此,我不知道如何正确使用F()表达式来处理此问题。
是否有更好的方法来获得与当前相同的结果?
答案 0 :(得分:1)
您可以通过使用Sum
[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()
或者如果当前模型的related_name
外键的BookInstance
不变,我们甚至可以使用:
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')
)