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

Я не уверен, каков наилучший способ суммирования значений из целочисленных полей внешнего ключа в связанной модели.Чтобы привести пример на основе учебника по 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 Ответ

1 голос
/ 14 мая 2019

Обновление одного объекта

Вы можете реализовать это с помощью Sum [Django-doc] :

from django.db.models import <b>Sum</b>

def update_times_checked_out(self):
    self.number_of_times_checked_out = BookInstance.objects.filter(
        book__id = self.id
    ).aggregate(
        <b>checked_out=Sum('number_of_times_checked_out')</b>
    )['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.<b>book_instance_set</b>.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 <b>OuterRef, Subquery</b>, Sum

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

Здесь все объекты MyModel будут обновлять поле number_of_times_checked_out в одном запросе.Конечно, этот запрос будет стоить на дороже , чем запрос для одного объекта, но обычно в несколько раз быстрее, чем обновление каждого объекта с помощью вышеуказанного метода в отдельности.

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

from sql_util.utils import <b>SubquerySum</b>

MyModel.objects.update(
    <b>number_of_times_checked_out=SubquerySum('bookinstance__checked_out')</b>
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...