Django:在相关模型中汇总外键值的正确方法

时间:2019-05-14 16:35:01

标签: python django django-models foreign-keys

我不确定在相关模型中将外键整数字段的值相加的最佳实践是什么。举一个基于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()表达式来处理此问题。

是否有更好的方法来获得与当前相同的结果?

1 个答案:

答案 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')
)