Perché prefetch_related () di django funziona solo con all () e non con filter ()?


89

supponiamo di avere questo modello:

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

Ora, se voglio guardare un sottoinsieme di foto in un sottoinsieme di album in modo efficiente. Lo faccio qualcosa del genere:

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

Questo fa solo due query, che è quello che mi aspetto (una per ottenere gli album, e poi una come `SELECT * IN photos WHERE photoalbum_id IN ().

È tutto bellissimo.

Ma se lo faccio:

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

Quindi fa un sacco di domande con WHERE format = 1! Sto facendo qualcosa di sbagliato o django non è abbastanza intelligente da rendersi conto che ha già recuperato tutte le foto e può filtrarle in Python? Giuro di aver letto da qualche parte nella documentazione che dovrebbe farlo ...


Risposte:


166

In Django 1.6 e versioni precedenti, non è possibile evitare le query aggiuntive. La prefetch_relatedchiamata memorizza efficacemente nella cache i risultati di a.photoset.all()per ogni album nel set di query. Tuttavia, a.photoset.filter(format=1)è un set di query diverso, quindi genererai una query aggiuntiva per ogni album.

Questo è spiegato nei prefetch_relateddocumenti. Il filter(format=1)è equivalente a filter(spicy=True).

Nota che potresti ridurre il numero o le query filtrando invece le foto in 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]

In Django 1.7, c'è un Prefetch()oggetto che ti permette di controllare il comportamento di 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

Per ulteriori esempi su come utilizzare l' Prefetchoggetto, vedere la prefetch_relateddocumentazione.


8

Dai documenti :

... come sempre con QuerySets, tutti i metodi concatenati successivi che implicano una query di database diversa ignoreranno i risultati precedentemente memorizzati nella cache e recupereranno i dati utilizzando una nuova query di database. Quindi, se scrivi quanto segue:

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

... quindi il fatto che pizza.toppings.all () sia stato precaricato non ti aiuterà - in effetti danneggia le prestazioni, dal momento che hai eseguito una query sul database che non hai usato. Quindi usa questa funzione con cautela!

Nel tuo caso, "a.photo_set.filter (format = 1)" viene trattato come una nuova query.

Inoltre, "photo_set" è una ricerca inversa, implementata tramite un gestore completamente diverso.


photo_setpuò essere precaricato anche con .prefetch_related('photo_set'). Ma l'ordine conta, come hai spiegato.
Risadinha

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.