2012-04-23 63 views
7

我試圖優化Django應用程序的數據庫查詢。下面是一個簡化的例子:django多對多字段:僅預取主鍵

class Label(models.Model): 
    name = models.CharField(max_length=200) 
    # ... many other fields ... 

class Thing(models.Model): 
    name = models.CharField(max_length=200) 
    labels = models.ManyToManyField(Label) 

我有獲取所有Label S和Thing秒和將其放入一個JSON數據結構,其中Thing S使用其id S(主密鑰)是指Label秒的功能。事情是這樣的:

{ 
    'labels': [ 
     { 'id': 123, 'name': 'label foo' }, 
     ... 
    ], 
    'things': [ 
     { 'id': 45, 'name': 'thing bar', 'labels': [ 123, ... ] }, 
     ... 
    ] 
} 

什麼是獲得使用Django這樣的數據結構的最有效的方法是什麼?假設我有大號Label S和牛逼Thing秒,平均Thing具有XLabel秒。

方法1:

data = {} 
data['labels'] = [model_to_dict(label) for label in Label.objects.all()] 
data['things'] = [model_to_dict(thing) for thing in Thing.objects.all()] 

這使得(1 + 1 + Ť)數據庫查詢,因爲model_to_dict(thing)需要爲每個單獨ThingLabel秒。

方法2:

data = {} 
data['labels'] = [model_to_dict(label) for label in Label.objects.all()] 
data['things'] = [model_to_dict(thing) for thing in 
        Thing.objects.prefetch_related('labels').all()] 

這使得只有(1 + 1 + 1)的數據庫查詢,由於Thing小號現在取出具有它們Label S IN單個附加查詢預取。

這仍然不令人滿意。prefetch_related('labels')將獲取相同的Label許多副本,而我只需要他們的id s。是否有任何方法只能預取Labelid?我試過prefetch_related('labels__id'),但沒有奏效。我還擔心,因爲T很大(數百),所以prefetch_related('labels')會導致帶有大型IN子句的SQL查詢。 大號小得多(< 10),這樣我就可以做到這一點,而不是:

方法3:

data = {} 
data['labels'] = [model_to_dict(label) for label in 
        Label.objects.prefetch_related('thing_set').all()] 
things = list(Thing.objects.all()) 
# plug in label ids by hand, and also fetch things that have zero labels 
# somehow 

這導致小IN條款,但仍然因爲prefetch_related('thing_set')取不盡如人意重複Thing s,如果Thing有多個Label s。

總結:

LabelThingManyToManyField連接。無論如何,我取全部Label s和Thing。那麼我怎樣纔能有效地獲取他們的多對多關係呢?

+1

也許嘗試使用m2m的中介模型?數據庫方案和其他任何東西都會保持不變,但是您只能從這個模型中獲取「相關」,並從中獲取標籤的ID。如果你將它與M2M的'through'參數聯繫起來,一些像add()這樣的方法將會被破壞,但你可以手動爲它提供'db_table'並且不要觸摸m2m字段,所以它應該可以工作。 – ilvar 2012-04-23 04:26:05

+0

感謝@ilvar,您的評論讓我回到了下面的答案。 – cberzan 2012-04-24 02:09:32

回答

7

我明白了。感謝ilvar,他對這個問題的評論指向我through tables

如果不指定通過模型明確的,還有一個隱含的通過你可以用它來直接訪問創建的用於保存關聯表 模型類 。它有三個字段鏈接 模型。

長話短說:

# Fetch all labels and things: 
labels = list(Label.objects.all()) 
things = list(Thing.objects.all()) 
# Fetch all label-thing pairs: 
labels_of = defaultdict(lambda: []) 
for pair in Thing.labels.through.objects.filter(label__in=labels): 
    labels_of[pair.thing_id].append(pair.label_id) 
# Put everything together: 
data = {} 
data['labels'] = [model_to_dict(label) for label in labels] 
data['things'] = [] 
for thing in things: 
    thing_dict = model_to_dict(thing, exclude='labels') 
    thing_dict['labels'] = labels_of[thing.id] 
    data['things'].append(thing_dict) 

這使得(1 + 1 + 1)的查詢,並且不反覆取東西。我還可以更改第一個for循環:

for pair in Thing.labels.through.objects.filter(thing__in=things): 

的情況下,我有更多的Label總比Thing s,這將導致以較小的IN子句的查詢。

Django-debug-toolbardebugsqlshell管理命令對於實際看到一段代碼正在查詢的查詢來說非常棒。