2016-08-12 4 views
1

Я создаю приложение Django, которое отслеживает награды, которые получает человек. Ниже приводится упрощенное представление двух моделей, которые у меня есть:Уменьшение количества запросов в Django

class AwardHolder(models.Model): 
    name = models.CharField() 

    def get_total_awards(self): 
     entries = self.award_set.all() 
     calc = entries.aggregate(sum=Sum('units_awarded')) 
     return calc.get('sum') or 0 


class Award(models.Model) 
    date_awarded = models.DateField() 
    units_awarded = models.IntegerField() 
    award_holder = models.ForeignKey(AwardHolder) 

Я создал сводную страницу для владельца награды, где он может увидеть его полные награды. Я использую функцию get_total_awards выше. Все работает хорошо, но затем я создал обзорную страницу, чтобы отобразить общие награды на каждого обладателя премии. Я использую функцию ниже, чтобы получить общее количество наград за обладателя премии:

def get_all_awards(): 
    qs = AwardHolder.objects.all() 
    awards = [] 
    for ah in qs: 
     awards.append((ah, ah.get_total_awards()) 
    return awards 

Это порождает большое количество запросов, так как он попадает в дб каждом проходе цикла. Есть ли способ, которым я могу использовать prefetch_related или какой-нибудь другой трюк db, чтобы уменьшить количество запросов db, не переписывая get_all_awards()?

Фактический код, который я использую, имеет намного больше полей и более сложный, чем этот пример. Есть около 10 функций, подобных get_all_awards(), поэтому переписывать его будет совсем немного. Однако для 100 обладателей премий мой код генерировал колоссальные 18000 запросов, поэтому я знаю, что мне это нужно как-то исправить.

+1

Я думаю, что это работа для аннотирования, а не совокупности. Что-то вроде 'AwardHolder.objects.annotate (Count ('award__units_awarded'))' –

+0

Я надеялся избежать этого, так как мне пришлось бы переделать много функций. Я полагаю, что функция get_total_awards внутри AwardHolder все равно будет применяться, если мне нужно только количество наград для одного обладателя премии? – Johan

+1

Для одного обладателя премии эта функция идеальна, если у вас не может быть меньше одного запроса: P Для больших чисел лучше делать все в одном запросе, а не в цикле, и выполнять несколько запросов, но вам нужно будет проверить, annotate' действительно делает то, что я думаю, что он делает –

ответ

1

Используйте менеджер модели всегда аннотирования AwardHolders

class AwardHolderQuerySet(models.QuerySet): 
    def all(self): 
     return self.annotate(Count('award__units_awarded')) 

class AwardHolder(models.Model): 
    name = models.CharField() 
    objects = AwardHolderQuerySet.as_manager() 
    ... 
1

get_all_awards необязательно звонить get_total_awards. Вы повторяете один и тот же запрос для каждого объекта вместо того, чтобы использовать функциональность, которую Django уже предоставляет: annotate.

Вы можете изменить get_all_awards для использования annotate и использовать get_total_awards только в том случае, если решение применяется к одному объекту.

def get_all_awards(): 
    qs = AwardHolder.objects.annotate(sum=Sum('award__units_awarded')) 
    awards = [] 
    for ah in qs: 
     awards.append((ah, ah.sum)) 
    return awards 

Вы можете даже опустить петлю for и использовать результат из QuerySet qs непосредственно.

Таким образом, вы получаете преимущество оптимизированного запроса, когда извлекаются несколько объектов и связанные с ними объекты, в отличие от запуска отдельных запросов для каждого объекта.

Смежные вопросы