Как уменьшить количество запросов в модели django методом has_relation? - PullRequest
7 голосов
/ 02 июня 2011

Вот два примера моделей Django. Обратите особое внимание на метод has_pet.

class Person(models.Model):
    name = models.CharField(max_length=255)

    def has_pet(self):
        return bool(self.pets.all().only('id'))

class Pet(models.Model):
    name = models.CharField(max_length=255)
    owner = models.ForeignKey(Person, blank=True, null=True, related_name="pets")

Проблема здесь в том, что метод has_pet всегда генерирует запрос. Если вы делаете что-то подобное.

p = Person.objects.get(id=1)
if p.has_pet():
    ...

Тогда вы на самом деле будете делать дополнительный запрос, чтобы просто проверить, есть ли у одного человека домашнее животное. Это большая проблема, если вам нужно проверить несколько человек. Он также будет генерировать запросы, если используется в таких шаблонах.

{% for person in persons %}
    {% if person.has_pet %}
        {{ person.name }} owns a pet
    {% else %}
        {{ person.name }} is petless
    {% endif %}
{% endfor %}

Этот пример фактически выполняет дополнительный запрос для каждого человека в наборе запросов лиц во время рендеринга шаблона.

Есть ли способ сделать это всего одним запросом или хотя бы выполнить менее одного дополнительного запроса на человека? Может быть, есть другой способ разработать это, чтобы вообще избежать этой проблемы.

Я думал о добавлении BooleanField в Person и о необходимости обновления этого поля при каждом сохранении или удалении питомца. Это действительно правильный путь?

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

1 Ответ

4 голосов
/ 02 июня 2011

Если вы хотите получить список всех людей с домашними животными, вы можете сделать это одним запросом:

Person.objects.exclude(pets=None)

Звучит так, будто вы хотите перебрать один и тот же список людей, поэтому использование аннотаций, вероятно, имело бы смысл:

for person in Person.objects.annotate(has_pet=Count('pets')):
     if person.has_pet: # if has_pet is > 0 this is True, no extra query

Было бы хорошо, если бы у Django был агрегат Exists, но его нет, и я не знаю, насколько сложно было бы добавить его. Вы, конечно, должны составить профиль и выяснить, подходит ли вам это.

Лично я бы, вероятно, просто сохранил has_pets как логическое значение для модели, это, вероятно, самый эффективный подход.

...