2012-04-23 24 views
7

Próbuję zoptymalizować zapytania do bazy danych dla aplikacji Django. Oto uproszczony przykład:django wiele-do-wielu pól: tylko podstawowe klucze preselekcji

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) 

Mam funkcję, która pobiera wszystkie Label s i Thing s i umieszcza je w strukturę danych JSON, w którym Thing s odnosić do Label s używając ich id s (kluczy podstawowych). Coś takiego:

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

Jaki jest najskuteczniejszy sposób uzyskania takiej struktury danych przy użyciu Django? Załóżmy, że ma LLabel S i TThing s, a średnia Thing ma xLabel s.

Metoda 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()] 

To sprawia, że ​​(1 + 1 + T) zapytań do bazy danych, ponieważ model_to_dict(thing) potrzeby, aby pobierać Label s dla każdego Thing indywidualnie.

Metoda 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()] 

To sprawia, że ​​tylko (1 + 1 + 1) zapytań do bazy danych, ponieważ Thing s pobrane teraz mają swoje Label s wstępnie pobrać w jednym dodatkowym zapytania.

To nadal nie jest zadowalające.prefetch_related('labels') pobierze wiele kopii tego samego Label, a ja potrzebuję ich tylko id s. Czy jest jakiś sposób, aby wstępnie pobrać id s tylko z? Próbowałem prefetch_related('labels__id'), ale to nie zadziałało. Obawiam się również, że ponieważ T jest duża (setki), to prefetch_related('labels') powoduje zapytanie SQL z dużą klauzulą ​​IN. L jest znacznie mniejszy (< 10), więc mogę to zrobić w zamian:

Metoda 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 

Skutkuje to mniejszym IN klauzuli, ale wciąż nie jest zadowalająca, ponieważ prefetch_related('thing_set') pobiera duplikat Thing s, jeśli Thing ma wiele Label s.

Podsumowanie:

Label i Thing są połączone ManyToManyField. Wciąż jednak pobieram s i . W jaki sposób mogę efektywnie pobierać relacje między wieloma osobami?

+1

Może spróbuj użyć modelu pośredniego dla m2m? Schemat bazy danych i wszystko inne pozostanie niezmienione, ale będziesz w stanie "pobrać" tylko ten model i pobrać z niego identyfikator etykiety. Jeśli podłączysz go do argumentu 'through' do M2M, niektóre metody, takie jak' add() 'zostaną złamane, ale możesz ręcznie podać' db_table' i nie dotykać pola m2m, więc powinno działać. – ilvar

+0

Dzięki @ilvar, Twój komentarz doprowadził mnie do odpowiedzi poniżej. – cberzan

Odpowiedz

7

Mam to. Dzięki ilvar, którego komentarz do pytania wskazał mi na through tables.

Jeśli nie określono wyraźne poprzez modelu, nadal istnieje niejawny przez klasę modelu można wykorzystać do bezpośredniego dostępu do tabeli stworzony, aby utrzymać związek. Ma trzy pola do połączenia modeli .

Krótko mówiąc:

# 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) 

To sprawia, że ​​(1 + 1 + 1) Zapytania i niczego nie pobierać wielokrotnie. Mogę również zmienić pierwszy dla pętli:

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

w razie mam więcej niż ThingLabel s s, co spowoduje w zapytaniu o mniejszej IN klauzuli. Polecenie zarządzania

Django-debug-toolbar jest znakomite, ponieważ faktycznie widzi zapytania wykonywane przez kod.

Powiązane problemy