为什么django的prefetch_related()只能用于all()而不能用于filter()?

时间:2012-10-19 12:05:16

标签: django orm filter prefetch

假设我有这个模型:

class PhotoAlbum(models.Model):
    title = models.CharField(max_length=128)
    author = models.CharField(max_length=128)

class Photo(models.Model):
    album = models.ForeignKey('PhotoAlbum')
    format = models.IntegerField()

现在,如果我想有效地查看相册子集中的一部分照片。我是这样做的:

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = a.photo_set.all()

这只有两个查询,这是我所期望的(一个是获取相册,然后是一个像`SELECT * IN photos WHERE photoalbum_id IN()。

一切都很棒。

但如果我这样做:

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = a.photo_set.filter(format=1)

然后它会对WHERE format = 1进行大量查询!我做错了什么或django不够聪明,意识到它已经取出所有的照片并可以在python中过滤它们?我发誓我在文档中的某个地方读到它应该这样做......

3 个答案:

答案 0 :(得分:145)

在Django 1.6及更早版本中,无法避免额外的查询。 prefetch_related调用有效地为查询集中的每个专辑缓存a.photoset.all()的结果。但是,a.photoset.filter(format=1)是一个不同的查询集,因此您将为每个专辑生成一个额外的查询。

prefetch_related文档中对此进行了解释。 filter(format=1)相当于filter(spicy=True)

请注意,您可以通过过滤python中的照片来减少数量或查询:

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = [p for p in a.photo_set.all() if p.format == 1]

在Django 1.7中,有一个Prefetch()对象允许您控制prefetch_related的行为。

from django.db.models import Prefetch

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related(
    Prefetch(
        "photo_set",
        queryset=Photo.objects.filter(format=1),
        to_attr="some_photos"
    )
)
for a in someAlbums:
    somePhotos = a.some_photos

有关如何使用Prefetch对象的更多示例,请参阅prefetch_related文档。

答案 1 :(得分:7)

来自docs

  

...与QuerySets一样,任何暗示不同数据库查询的后续链接方法都将忽略以前缓存的结果,并使用新的数据库查询检索数据。所以,如果你写下面的内容:

     

pizzas = Pizza.objects.prefetch_related('toppings')   [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]

     

...那么pizza.toppings.all()已被预取的事实对你没有帮助 - 实际上它会伤害性能,因为你已经完成了一个你没有使用过的数据库查询。所以请谨慎使用此功能!

在您的情况下,“a.photo_set.filter(format = 1)”被视为新查询。

此外,“photo_set”是反向查找 - 通过不同的管理器实现。

答案 2 :(得分:0)

如果要与filter()一起使用,可以使用select_related

results = Geography.objects.filter(state__pk = 1).select_related('country')
results.query

更多信息:https://docs.djangoproject.com/en/3.1/ref/models/querysets/#select-related