2009-05-12 18 views
14

を設定するには、私は2つのモデルがあるだろう:DjangoのORM:選択関連が

class Poll(models.Model): 
    category = models.CharField(u"Category", max_length = 64) 
    [...] 

class Choice(models.Model): 
    poll = models.ForeignKey(Poll) 
    [...] 

ポーリングオブジェクトを考えると、私はその選択肢を照会することができます

poll.choice_set.all() 

しかし、照会するユーティリティ関数があります世論調査のセットからすべての選択肢?

実は、私は次のようなものを探しています(サポートされていない、と私はそれができるか模索していない):

polls = Poll.objects.filter(category = 'foo').select_related('choice_set') 
for poll in polls: 
    print poll.choice_set.all() # this shouldn't perform a SQL query at each iteration 

は、私は私を助けるために(醜い)関数を作りましたそれを達成:

次のように使用されている
def qbind(objects, target_name, model, field_name): 
    objects = list(objects) 
    objects_dict = dict([(object.id, object) for object in objects]) 
    for foreign in model.objects.filter(**{field_name + '__in': objects_dict.keys()}): 
     id = getattr(foreign, field_name + '_id') 
     if id in objects_dict: 
      object = objects_dict[id] 
      if hasattr(object, target_name): 
       getattr(object, target_name).append(foreign) 
      else: 
       setattr(object, target_name, [foreign]) 
    return objects 

polls = Poll.objects.filter(category = 'foo') 
polls = qbind(polls, 'choices', Choice, 'poll') 
# Now, each object in polls have a 'choices' member with the list of choices. 
# This was achieved with 2 SQL queries only. 

は簡単に何かがすでにPありDjangoによってrovided?少なくとも、より良い方法で同じことをするスニペット。

この問題は通常どのように処理しますか?

+0

おそらく、qbind関数を実行することができます。しかし、それはカスタムマネージャーでパッケージ化する意味があるかもしれません - http://docs.djangoproject.com/en/dev/topics/db/managers/#id2 – NathanD

答えて

11

更新:Django 1.4以降、この機能は組み込まれています:prefetch_relatedを参照してください。

最初の答え:作業中のアプリケーションを作成し、プロファイリングし、N個のクエリが実際にデータベースと負荷シナリオのパフォーマンス上の問題であることを実証するまで、qbindのようなものを書くのは時間を無駄にしないでください。

しかし、おそらくそれをやったことがあります。したがって、2番目の答え:qbind()は、必要な処理を行いますが、カスタムQuerySetサブクラスにパッケージ化されていれば、カスタムQuerySetのインスタンスを返すManagerサブクラスが追加されます。理想的には、それらを汎用的にし、逆の関係に再利用できるようにすることもできます。同様の問題を解決しますが、一般的な外部キーの場合のために、関係を逆にしないマネージャー/クエリセット技術、this snippetを参照してください、の例えば

Poll.objects.filter(category='foo').fetch_reverse_relations('choices_set') 

:次に、あなたのような何かを行うことができます。あなたのqbind()関数の中身をそこに表示されている構造体と組み合わせて、あなたの問題を本当にいい解決策にすることは、それほど難しいことではありません。

14

私はあなたが言っていることは、「私はすべての選択肢を投票のセットにしたい」と思います。もしそうなら、これを試してみてください。

polls = Poll.objects.filter(category='foo') 
choices = Choice.objects.filter(poll__in=polls) 
+0

+1私はこの機能について知らなかった!どのように完全にエレガント! –

+1

これは私が 'qbind'関数の始めに行うことです。しかし、実際には、選択肢全体ではなく、投票ごとの選択肢*が必要です。たとえば、テンプレートに投票のリストを表示したい場合、それぞれの選択肢とともに、私は投票ごとにデータベースにヒットしたくありません。 'qbind'関数のポイントは、' polls'と 'choices'データを結合してそれを達成することです。 –

1

私が何をやろうとしていることは用語子データの「積極的なロード」だと思う - 各ポーリングのためにあなたが子リストをロードしている意味(choice_set)が、すべてでDBへの最初のクエリーを実行するので、後でたくさんのクエリーを作成する必要はありません。

これが正しい場合は、あなたが探していることは「select_related」は - https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related

を参照してください私はあなたが「select_related」しようとしたが、それは動作しませんでした気づきました。あなたは 'select_related'とフィルタを実行してみることができます。それはそれを修正するかもしれない。


更新:これは機能しません。以下のコメントを参照してください。

+0

select_relatedは、Choiceに対してクエリを実行していて、対応する各ポーリングをあらかじめ読み込みたい場合に便利です。しかし、ここでは、select_relatedでサポートされていない逆が欲しいです(対応するSQL問合せについて考えると、たくさんのデータを複製せずに1つの問合せで行うことはできません)。これは2つの問合せで行う必要があります。 –

+0

うん、あなたの権利。それを見てごめんなさい申し訳ありません。 'choice_set'はクエリが評価されるまで利用できないため、クエリが存在することを認識しません。フォローアップのために+1 – NathanD

16

この機能はDjango 1.4で利用可能になり、prefetch_related() QuerySet関数が導入されました。この関数は、提案されたqbind関数によって実行されることを効果的に行います。すなわち、 2つのクエリが実行され、Pythonの土地で結合が行われますが、これはORMによって処理されます。

元のクエリ要求は今なる:

polls = Poll.objects.filter(category = 'foo').prefetch_related('choice_set') 

次のサンプルコードに示されているように、pollsクエリセットは、任意の更なるデータベースのヒットを必要とせずに、Poll当たり全Choiceオブジェクトを得るために使用することができる。

for poll in polls: 
    for choice in poll.choice_set: 
     print choice 
+0

+1。 googleからこのページにつまずく誰のためのベストソリューション。 –

+0

私はDjango 1.6を使用し、Pythonシェルで使用する場合、タイプエラー関連のマネージャオブジェクトを反復不可能にします。私はフレデリックと同じことをしました:選択肢には外部キー・ポーリングがあるので、1対nの関係があります。投票と選択 – Timo

+0

@Timo 'poll.choice_set.all()'を呼び出す必要があります。 –

関連する問題