1. ホーム
  2. django

[解決済み] なぜ django の prefetch_related() は all() でしか動作せず、filter() では動作しないのでしょうか?

2023-01-31 14:49:07

質問

このようなモデルがあるとします。

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つはアルバムを取得し、次に `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 でフィルタリングできることに気づくほど賢くないのでしょうか?ドキュメントのどこかに、そうすることになっていると書いてあったはずなのですが......。

どのように解決するのですか?

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では、Djangoに対応するために 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 のドキュメントを参照してください。