【问题标题】:Django: Proper method of totalling values from foreignkeys in related modelDjango:从相关模型中的外键合计值的正确方法
【发布时间】:2019-10-01 17:54:50
【问题描述】:

我不确定在相关模型中对外键整数字段求和的最佳实践方法是什么。举一个基于django教程构建库的例子:

假设您已经创建了书籍和书籍实例库,每本书都包含图书馆中的许多真实书籍实例。您在 BookInstance 上维护检出次数的整数字段。通过这种方式,您可以查看每个特定实例已签出的次数。现在,您想要汇总书籍模型中实例被检出的所有时间,以便您可以快速参考图书馆中检出次数最多的书籍。在 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 添加到数据库中,这可能会开始变得非常慢。我也知道如果这被不同的 BookInstances 同时调用两次,这可能会有一个竞争条件。

我知道 F() 表达式,并在 +=1 的简单增量情况下使用它们,但在我的实际情况下,此更新不仅可以处理常量值增量,还可以处理值的大幅跳跃(可能是 +1/10 /500 在任何时候仅来自 1 个实例)。因此我不知道如何正确使用 F() 表达式来处理这个问题。

有没有更好的方法来获得与我目前相同的结果?

【问题讨论】:

    标签: python django django-models foreign-keys


    【解决方案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()

    或者如果当前模型的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')
    )

    【讨论】:

    • 好答案,只是想指出 django-sql-utils (pypi.org/project/django-sql-utils) 包简化了子查询,所以你可以只使用number_of_times_checked_out=SubquerySum('book')
    猜你喜欢
    • 2014-08-22
    • 2022-07-11
    • 2021-11-16
    • 2012-05-07
    • 2016-01-05
    • 2018-04-22
    • 1970-01-01
    • 2023-03-19
    • 2013-11-09
    相关资源
    最近更新 更多