2009-05-12 68 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推出?或者至少,一個片段以更好的方式做同樣的事情。

你通常如何處理這個問題?

+0

也許你的qbind函數是可以完成的最好的。但是將它打包到自定義管理器中可能有意義 - http://docs.djangoproject.com/en/dev/topics/db/managers/#id2 – NathanD 2009-05-12 16:11:41

回答

11

更新:由於Django 1.4,此功能內置於:請參閱prefetch_related

第一個答案:不要浪費時間,直到你已經寫了一個工作的應用程序編寫類似qbind,異形的,並證明了N次查詢實際上是爲你的數據庫和負載情況下性能問題。

但也許你已經做到了。第二個答案:qbind()完成你需要做的事情,但是如果打包在自定義的QuerySet子類中,並且附帶的Manager子類返回自定義QuerySet的實例,那將更加通俗易懂。理想情況下,你甚至可以使它們具有通用性,並可用於任何反向關係。然後,你可以這樣做:

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

對於經理/查詢集技術的一個例子,看看this snippet,解決了類似的問題,但對於通用外鍵的情況下,不能反向關係。將你的qbind()函數的膽量與顯示的結構相結合並不難,以便爲你的問題提供一個非常好的解決方案。

14

我覺得你說的話是「我想爲一組投票的所有選擇。」如果是的話,試試這個:

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

+1我對此功能不瞭解!多麼完全優雅! – 2009-05-12 15:04:09

+1

這就是我在'qbind'函數開始時所做的。但實際上我希望選擇*每個*民意調查,而不是整套選擇。例如,如果我想要顯示模板上的民意調查列表以及每個民意調查的選項,我不希望爲每個民意調查都選中數據庫。 「qbind」函數的要點是將您的「民意調查」和「選擇」數據結合在一起以實現這一點。 – 2009-05-12 15:20:56

1

我想你正在嘗試做的是「預先加載」一詞兒童的數據 - 這意味着你加載每個輪詢子列表(choice_set),但所有的首先查詢數據庫,以便以後不必進行大量查詢。

如果這是正確的,那麼你所追求的是「select_related」什麼 - 看https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related

我注意到你試過「select_related」,但沒有奏效。你可以嘗試做'select_related'然後過濾器。這可能會解決它。


更新:這不起作用,請參閱下面的註釋。

+0

如果查詢Choice並希望預先加載每個相應的Poll,select_related將會很有用。但是在這裏,我需要select_related不支持的相反方式(考慮相應的SQL查詢,不能在不重複大量數據的情況下在一個查詢中完成)。這應該使用2個查詢完成。 – 2009-05-12 15:43:22

+0

是的,你的權利。對不起,沒有看到。由於'choice_set'在查詢被評估之前不可用,所以它不能識別它甚至存在。 – NathanD 2009-05-12 16:00:56

16

時間已過,現在可以在Django 1.4中使用此功能,並引入了prefetch_related() QuerySet函數。該功能可以有效地執行建議的qbind功能所執行的功能。即。執行兩個查詢,並在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進行跟蹤。任何人從谷歌絆跌到這個頁面的最佳解決方案。 – 2013-08-01 14:46:28

+0

我使用Django 1.6,並且在python shell中使用類型錯誤相關的管理器對象時不可迭代。我做了和弗雷德裏克完全一樣的東西:選擇有外部關鍵調查,所以有一個1到n的關係。民意調查和選擇 – Timo 2014-05-11 08:18:57

+0

@Timo您應該調用'poll.choice_set.all()'。 – 2015-12-04 22:50:51