How to reduce queries in the has_relation method of a django model?

Here are two examples of Django models. Pay particular attention to the has_pet method.

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") 

The problem is that the has_pet method always generates a request. If you do something like this.

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

Then you will really make an additional request to check if one person has a pet. This is a big problem if you need to check out several people. It will also generate queries if they are used in such patterns.

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

This example will actually execute an additional query for each person in the set of facial queries while it displays the template.

Is there a way to do this with just one request, or at least make less than one additional request per person? Perhaps there is another way to develop this in order to avoid the problem altogether.

I thought about adding a BooleanField to Person and updating this field whenever the pet is saved or deleted. Is this really the right way?

In addition, I already have memcached setup correctly, so these requests only happen if the results are not cached yet. I want to delete queries first of all for even more optimization.

+7
source share
1 answer

If you need a list of all people with pets, you can do this in one query:

 Person.objects.exclude(pets=None) 

It sounds like you want to iterate over a single list of people using annotations, it probably makes sense:

 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 

It would be nice if Django had an Exists aggregate, but it is not, and I do not know how difficult it would be to add it. Of course you need a profile, and find out if this works for you.

Personally, I would just keep has_pets as logical for the model, this is probably the most efficient approach.

+4
source

All Articles