如何减少django模型中的查询has_relation方法?

Apr*_*che 7 python django django-models django-queryset

这里有两个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")
Run Code Online (Sandbox Code Playgroud)

这里的问题是has_pet方法总是生成一个查询.如果你做这样的事情.

p = Person.objects.get(id=1)
if p.has_pet():
    ...
Run Code Online (Sandbox Code Playgroud)

那么你实际上会做一个额外的查询,只是为了检查一个人是否有宠物.如果你需要检查多个人,这是一个大问题.如果在这样的模板中使用它,它也会生成查询.

{% for person in persons %}
    {% if person.has_pet %}
        {{ person.name }} owns a pet
    {% else %}
        {{ person.name }} is petless
    {% endif %}
{% endfor %}
Run Code Online (Sandbox Code Playgroud)

此示例实际上将在呈现模板时为人员查询集中的每个人执行额外查询.

有没有办法只用一个查询来执行此操作,或者至少每个人执行少于一个额外查询?也许还有另一种设计方法可以完全避免这个问题.

我想到向Person添加一个BooleanField,并在保存或删除宠物时更新该字段.这真的是正确的方法吗?

此外,我已经正确地进行了memcached设置,因此只有在结果尚未缓存的情况下才会发生这些查询.我想首先删除查询以进行更大程度的优化.

zee*_*kay 4

如果您想要所有养宠物的人的列表,您可以在单个查询中完成:

Person.objects.exclude(pets=None)
Run Code Online (Sandbox Code Playgroud)

听起来您想迭代单个人员列表,使用注释可能会有意义:

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
Run Code Online (Sandbox Code Playgroud)

如果 Django 有一个聚合就好了Exists,但它没有,而且我不知道添加一个聚合会有多困难。当然,您应该进行分析,并弄清楚这是否适合您。

就我个人而言,我可能只是将其存储has_pets为模型上的布尔值,这可能是最有效的方法。