djangoのprefetch_related()がall()でのみ機能し、filter()では機能しないのはなぜですか?


89

私がこのモデルを持っているとしましょう:

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

これは2つのクエリのみを実行します。これは私が期待するものです(1つはアルバムを取得し、もう1つは `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でそれらをフィルタリングできることに気付くほど賢くありませんか?ドキュメントのどこかでそれを行うことになっていることを読んだことを誓います...


回答:


166

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ドキュメントを参照してください。


8

ドキュメントから:

... 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」は逆引き参照であり、まったく別のマネージャーを介して実装されます。


photo_setでプリフェッチすることもできます.prefetch_related('photo_set')。しかし、あなたが説明したように、順序は重要です。
リサディーニャ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.