2012-04-23 5 views
7

장고 앱에 대한 데이터베이스 쿼리를 최적화하려고합니다.django many-to-many 필드 : 프리 페치 기본 키

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들과 Thing의를 가져와 Thing의 자신의 id의 (기본 키)를 사용하여 Label의 참조하는 JSON 데이터 구조로 그들을두고하는 기능을 가지고 : 여기에 간단한 예입니다. 이런 식으로 뭔가 :

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

장고를 사용하여 이러한 데이터 구조를 얻는 가장 효율적인 방법은 무엇입니까? 내가 LLabel들과 TThing의이 있다고 가정하고, 평균 ThingXLabel의가 있습니다.

방법 1 : model_to_dict(thing) 필요가 개별적으로 ThingLabel의를 가져올 수 있기 때문에

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 + T) 데이터베이스 쿼리를합니다.

방법 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의는 하나의 추가 쿼리에서 프리 페치가 있기 때문이다.

이것은 여전히 ​​만족스럽지 않습니다.prefetch_related('labels')id 만 필요하지만 동일한 Label 사본을 여러 개 가져옵니다. Labelid 초만 미리 가져올 수 있나요? 내가 시도한 prefetch_related('labels__id')하지만 그 작동하지 않았다. 또한 이 큽니다 (수백)이기 때문에 prefetch_related('labels')은 큰 IN 절이있는 SQL 쿼리를 생성합니다. L은 (< 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') 페치 때문에 만족하지 않습니다 ThingLabel이 여러 개인 경우 Thing이 중복됩니다.

요약 :

LabelThingManyToManyField 의해 접속된다. 어쨌든 모두LabelThing을 가져오고 있습니다. 그렇다면 어떻게 다 - 대 - 다 관계를 효과적으로 가져올 수 있습니까?

+1

아마도 m2m에 대한 중간 모델을 사용해 보시겠습니까? DB 스키마와 다른 것은 동일하게 유지되지만이 모델 만 'fetch_related'할 수 있고 레이블의 ID를 가져올 수 있습니다. M2M에'through' 인수를 연결하면'add()'와 같은 일부 메소드가 깨지지만 수동으로'db_table'을 제공 할 수 있고 m2m 필드를 건드리지 않아도됩니다. – ilvar

+0

감사합니다 @ilvar, 귀하의 의견은 아래의 답변을 이끌었다. – cberzan

답변

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) 쿼리를 만들고, 반복 아무것도 가져 오지 않습니다. 나는 작은 IN 절이있는 쿼리가 발생합니다 Thing의,보다 더 Label의이 경우

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

: 나는 또한에 루프 처음을 변경할 수 있습니다.

Django-debug-toolbardebugsqlshell 관리 명령은 실제로 코드 조각이 만드는 쿼리를 보는 데 유용합니다.