Джанго: Правильный метод суммирования значений от иностранных ключей в родственной модели

Я не уверен, каков наилучший способ суммирования значений из целочисленных полей внешнего ключа в связанной модели. Чтобы привести пример на основе учебника по django для создания библиотеки:

Допустим, вы создали свою библиотеку книг и книг с каждой книгой, содержащей много реальных книг, находящихся в библиотеке. Вы поддерживаете целочисленное поле в BookInstance количество проверенных раз. Таким образом, вы можете увидеть, сколько раз был извлечен каждый конкретный экземпляр. Теперь вы хотите суммировать все случаи, когда экземпляры были извлечены в модели книги, чтобы вы могли получить краткий справочник о том, какая книга была извлечена больше всего в вашей библиотеке. Какова была бы лучшая практика для суммирования всех этих значений в модели Книги?

В настоящее время у меня есть рабочее решение для моего случая, в котором целое поле хранится несколько раз, извлеченных из самой модели книги, а затем у меня есть функция в модели книги, которую я вызываю из модели 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 (), чтобы справиться с этим.

Есть ли лучший способ получить тот же результат, что у меня сейчас?

Всего 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 внешнего ключа 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 в одном запросе. Конечно, этот запрос будет дороже, чем запрос для одного объекта, но обычно в несколько раз быстрее, чем обновление каждого объекта с помощью вышеуказанного метода в отдельности.

Вы можете, как говорит django-sql-utils , а затем работать с SubquerySum , например:

from sql_util.utils import SubquerySum

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

Есть идеи?

10000