17

インスタンスまたはインスタンスのいずれかを指すモデルBoxがあるとします。とは、それぞれtoとForeignKeysを持っています。アクセスする必要のあるesのリストを表示したい。できるだけ少ないDBクエリでこれを行うにはどうすればよいですか?GenericForeignKeyAppleChocolateAppleChocolateFarmFactoryBoxFarmFactory

最小限の実例:

class Farm(Model):
    ...

class Apple(Model):
    farm = ForeignKey(Farm)
    ...

class Factory(Model):
    ...

class Chocolate(Model):
    factory = ForeignKey(Factory)
    ...

class Box(Model)
    content_type = ForeignKey(ContentType)
    object_id = PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    ...

    def __unicode__(self):
        if self.content_type == ContentType.objects.get_for_model(Apple):
            apple = self.content_object
            return "Apple {} from Farm {}".format(apple, apple.farm)
        elif self.content_type == ContentType.objects.get_for_model(Chocolate):
            chocolate = self.content_object
            return "Chocolate {} from Factory {}".format(chocolate, chocolate.factory)

これが私が試したいくつかのことです。これらすべての例で、Nはボックスの数です。ContentTypeクエリカウントは、AppleChocolateがすでにキャッシュされていることを前提としているため、get_for_model()呼び出しはDBにヒットしません。

1)ナイーブ:

print [box for box in Box.objects.all()]

これは、 1(ボックスをフェッチ)+ N(ボックスごとにAppleまたはChocolateをフェッチ)+ N (AppleごとにFarmをフェッチし、ChocolateごとにFactoryをフェッチ)クエリを実行します。

2)はであるselect_relatedため、ここでは役に立ちません。Box.content_objectGenericForeignKey

3)django 1.4以降、 sprefetch_relatedをフェッチできGenericForeignKeyます。

print [box for box in Box.objects.prefetch_related('content_object').all()]

これは、 1(ボックスをフェッチ)+ 2(すべてのボックスのアップルとチョコレートをフェッチ)+ N(各アップルのファームと各チョコレートのファクトリーをフェッチ)クエリを実行します。

4)どうやらprefetch_related、GenericForeignKeysのForeignKeysをフォローするほど賢くはありません。私が試してみると:

print [box for box in Box.objects.prefetch_related( 'content_object__farm', 'content_object__factory').all()]

Chocolateオブジェクトにフィールドがないことfarm、およびその逆のことは当然のことです。

5)私ができること:

apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes_with_apples = Box.objects.filter(content_type=apple_ctype).prefetch_related('content_object__farm')
boxes_with_chocolates = Box.objects.filter(content_type=chocolate_ctype).prefetch_related('content_object__factory')

これは、 1(ボックスをフェッチ)+ 2(すべてのボックスのリンゴとチョコレートをフェッチ)+ 2(すべてのリンゴのファームとすべてのチョコレートのファクトリーをフェッチ)クエリを実行します。欠点は、2つのクエリセット(boxes_with_applesboxes_with_chocolates)を手動でマージして並べ替える必要があることです。私の実際のアプリケーションでは、これらのボックスをページ付けされたModelAdminに表示しています。このソリューションをそこに統合する方法は明らかではありません。たぶん私はこのキャッシングを透過的に行うためのカスタムPaginatorを書くことができますか?

6)これに基づいてO(1)クエリも実行する何かをまとめることができます。_content_object_cacheしかし、それを避けることができれば、内部()をいじりたくありません。

要約:ボックスを印刷するには、GenericForeignKeyのForeignKeysにアクセスする必要があります。O(1)クエリでNボックスを印刷するにはどうすればよいですか?(5)私ができる最善のことですか、それとももっと簡単な解決策がありますか?

ボーナスポイント:このようなクエリを簡単にするために、このDBスキーマをどのようにリファクタリングしますか?

4

1 に答える 1

10

のようなものを手動で実装し、データベースクエリで結合するprefetch_selectedDjangoのメソッドを使用できます。select_related

apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes = Box.objects.all()
content_objects = {}
# apples
content_objects[apple_ctype.id] = Apple.objects.select_related(
    'farm').in_bulk(
        [b.object_id for b in boxes if b.content_type == apple_ctype]
    )
# chocolates
content_objects[chocolate_ctype.id] = Chocolate.objects.select_related(
    'factory').in_bulk(
        [b.object_id for b in boxes if b.content_type == chocolate_ctype]
    )

これにより、3つのクエリのみが実行されます(get_for_modelクエリは省略されます)。このin_bulkメソッドは、{id:model}の形式でdictを返します。したがって、content_objectを取得するには、次のようなコードが必要です。

content_obj = content_objects[box.content_type_id][box.object_id]

ただし、このコードがO(5)ソリューションよりも高速であるかどうかはわかりません。これは、ボックスクエリセットに対して追加の反復が必要であり、WHERE id IN (...)ステートメントを含むクエリも生成するためです。

ただし、ボックスモデルのフィールドのみでボックスを並べ替えるとcontent_objects、ページネーション後に口述を入力できます。content_objectsしかし、あなたは__unicode__どういうわけかに渡す必要があります。

このようなクエリを簡単にするために、このDBスキーマをどのようにリファクタリングしますか?

同様の構造になっています。に保存content_objectしますBoxが、の代わりにobject_idとをcontent_object使用ForeignKey(Box)します。には、AppleまたはChocolateモデルを返すメソッドがあります。この場合はを使用できますが、ほとんどのユースケースでは、content_typeでボックスをフィルタリングします。したがって、5番目のオプションと同じ問題があります。しかし、prefetch_selectedがなかったときにDjango1.2でプロジェクトを開始しました。AppleChocolateBoxget_objectselect_related

ファーム/ファクトリの名前をcreatorなどの一般名に変更すると、prefetch_relatedは機能しますか?

オプションについて6

塗りつぶしに対しては何も言えません_content_object_cache。内部を扱いたくない場合は、カスタムプロパティを入力してから、

apple = getattr(self, 'my_custop_prop', None)
if apple is None:
    apple = self.content_object
于 2012-10-19T14:10:57.330 に答える