Как уменьшить количество запросов в методе has_relation модели django?

#python #django #django-models #django-queryset

#python #django #django-модели #django-набор запросов

Вопрос:

Вот два примера моделей 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 %}
  

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

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

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

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

Комментарии:

1. Я думаю, что ваше предложение — лучший подход: логическое поле, которое отслеживает, есть ли у пользователя домашнее животное или нет. Pet — это отдельная таблица в БД, поэтому вам всегда придется выполнять к ней какой-то запрос, чтобы узнать, есть ли у человека домашнее животное

2. Хотя, теперь, когда я думаю об этом, если вы запрашиваете все записи в таблице Pet, вам не нужно выполнять дополнительный запрос для каждого пользователя. т. Е. если Pet.objects.all(owner.id=id).exists() не должен вызывать другой запрос в БД

Ответ №1:

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

 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 в качестве логического значения в модели, это, вероятно, наиболее эффективный подход.

Комментарии:

1. Хорошо, я могу понять, как вы могли подумать, что это то, о чем я спрашивал, основываясь на моем примере, но я это уже знал. Я обновлю свой пример, чтобы не создавать неверного впечатления.

2. Аннотирование определенно помогает сократить его до одного запроса. К сожалению, этот один запрос выполняется очень медленно, когда у меня есть четыре или пять таких проверок has_. Это логическое поле!