¿Por qué prefetch_related () de django solo funciona con all () y no con filter ()?

89

supongamos que tengo este modelo:

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

Ahora, si quiero ver un subconjunto de fotos en un subconjunto de álbumes de manera eficiente. Lo hago algo como esto:

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

Esto hace solo dos consultas, que es lo que espero (una para obtener los álbumes y luego otra como `SELECT * IN photos WHERE photoalbum_id IN ().

Todo esta bien.

Pero si hago esto:

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

¡Entonces hace un montón de consultas con WHERE format = 1! ¿Estoy haciendo algo mal o django no es lo suficientemente inteligente como para darse cuenta de que ya ha obtenido todas las fotos y puede filtrarlas en Python? Juro que leí en algún lugar de la documentación que se supone que debe hacer eso ...

Timmmm
fuente
posible duplicado de Filter en prefetch_related en Django
akaihola

Respuestas:

166

En Django 1.6 y versiones anteriores, no es posible evitar las consultas adicionales. La prefetch_relatedllamada almacena en caché de forma efectiva los resultados de a.photoset.all()para cada álbum del conjunto de consultas. Sin embargo, a.photoset.filter(format=1)es un conjunto de consultas diferente, por lo que generará una consulta adicional para cada álbum.

Esto se explica en los prefetch_relateddocumentos. El filter(format=1)es equivalente a filter(spicy=True).

Tenga en cuenta que podría reducir el número de consultas filtrando las fotos en Python en su lugar:

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]

En Django 1.7, hay un Prefetch()objeto que te permite controlar el comportamiento de 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

Para obtener más ejemplos de cómo utilizar el Prefetchobjeto, consulte los prefetch_relateddocumentos.

Alasdair
fuente
8

De los documentos :

... como siempre con QuerySets, cualquier método encadenado posterior que implique una consulta de base de datos diferente ignorará los resultados previamente almacenados en caché y recuperará los datos utilizando una consulta de base de datos nueva. Entonces, si escribe lo siguiente:

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

... entonces el hecho de que pizza.toppings.all () se haya obtenido previamente no le ayudará; de hecho, perjudica el rendimiento, ya que ha realizado una consulta de base de datos que no ha utilizado. ¡Utilice esta función con precaución!

En su caso, "a.photo_set.filter (format = 1)" se trata como una consulta nueva.

Además, "photo_set" es una búsqueda inversa, implementada a través de un administrador diferente.

Ngure Nyaga
fuente
photo_settambién se puede obtener previamente con .prefetch_related('photo_set'). Pero el orden importa, como ha explicado.
Risadinha