2009-10-29 13 views
5

Mam następującą strukturę modeluDjango ORM: optymalizacja zapytań obejmujących wiele do wielu relacji

class Container(models.Model): 
    pass 

class Generic(models.Model): 
    name = models.CharacterField(unique=True) 
    cont = models.ManyToManyField(Container, null=True) 
    # It is possible to have a Generic object not associated with any container, 
    # thats why null=True 

class Specific1(Generic): 
    ... 

class Specific2(Generic): 
    ... 

... 

class SpecificN(Generic): 
    ... 

powiedzieć, że trzeba pobrać wszystkie Specific modele Type, które mają związek z danym pojemniku.

SQL jest mniej lub bardziej banalny, ale to nie jest pytanie. Niestety, nie mam dużego doświadczenia w pracy z ORMami (w szczególności ORM Django), więc być może brakuje mi tutaj wzoru.

Kiedy odbywa się w sposób brute-force, -

c = Container.objects.get(name='somename') # this gets me the container 
items = c.generic_set.all() 
# this gets me all Generic objects, that are related to the container 
# Now what? I need to get to the actual Specific objects, so I need to somehow 
# get the type of the underlying Specific object and get it 
for item in items: 
    spec = getattr(item, item.get_my_specific_type()) 

skutkuje ogromną ilością db trafień (po jednym dla każdej Generic rekordu, który odnosi się do pojemnika), więc nie jest to oczywiście droga aby to zrobić. Teraz to może być może być wykonane przez coraz SpecificX obiektów bezpośrednio:

s = Specific1.objects.filter(cont__name='somename') 
# This gets me all Specific1 objects for the specified container 
... 
# do it for every Specific type 

że droga db będzie hit raz dla każdego typu Specific (dopuszczalne, chyba).

Wiem, że .select_related() nie działa z relacjami m2m, więc nie ma tu zbytniej pomocy.

Aby powtórzyć, wynik końcowy musi być zbiorem obiektów SpecificX (nie generycznych).

+0

W retrospekcji, pytanie wydaje się nieco bezcelowe mnie teraz jak już pod warunkiem, że jedyną możliwą odpowiedź. W końcu nie ma sposobu na uzyskanie wspólnego zestawu wyników z wielu tabel z dowolnymi polami. Ok, oczywiście, to jest sposób, ale jest brzydki i wymaga dynamicznego sql i/lub "wybierz *". Myślę. – shylent

+0

Twoje pytanie tutaj naprawdę nie ma nic wspólnego z optymalizacją relacji ManyToMany i wszystko, co ma związek z optymalizacją zapytań dotyczących dziedziczenia z wielu tabel. To naprawdę trudny problem. –

+0

Teraz, gdy o tym myślę, co skłoniło mnie do uwierzenia, że ​​ta kwestia dotyczy relacji m2m, jest tak naprawdę faktem, że selekcja dobrana nie prowadzi przez wiele do wielu relacji. – shylent

Odpowiedz

2

Myślę, że już nakreśliłeś dwie proste możliwości. Wykonaj jedno zapytanie filtrujące w stosunku do Generic, a następnie przesyłaj każdy element do jego podtypu Specific (wyniki w zapytaniach n + 1, gdzie n oznacza liczbę zwróconych elementów) lub jeśli tworzysz osobne zapytanie dla każdej konkretnej tabeli (wyniki w k zapytania, gdzie k jest liczbą określonych typów).

Rzeczywiście warto przeprowadzić analizę porównawczą, aby zobaczyć, który z nich jest szybszy w rzeczywistości. Drugi wydaje się lepszy, ponieważ jest (prawdopodobnie) mniejszą liczbą zapytań, ale każde z tych zapytań musi wykonać sprzężenie z pośrednim stołem m2m. W pierwszym przypadku wystarczy jedno zapytanie o dołączenie, a następnie wiele prostych. Niektóre bazy danych działają lepiej z wieloma małymi zapytaniami niż mniej, bardziej złożone.

Jeśli drugi jest rzeczywiście znacznie szybszy w przypadku użycia i chcesz wykonać dodatkową pracę w celu oczyszczenia kodu, powinno być możliwe napisanie niestandardowej metody zarządzania dla modelu ogólnego, który "wstępnie pobiera "wszystkie dane podtypów z odpowiednich tabel szczegółowych dla danego zestawu zapytań, używając tylko jednego zapytania na tabelę podtypów; podobny do tego, jak this snippet optymalizuje ogólne klucze obce przy użyciu zbiorczego pobierania wstępnego. To da ci te same zapytania, co twoja druga opcja, ze składnią DRYer twojej pierwszej opcji.

+0

Nie wiem, kto to zignorował. Tak, przyjrzę się możliwości napisania niestandardowego menedżera, jednak, jak już wspomniałem, moje doświadczenie z ORM jest bardzo ograniczone (nie mam problemu z SQL), więc wnętrza wciąż są trochę czarna skrzynka dla mnie. W każdym razie pójdę zobaczyć, co mogę zrobić. – shylent

1

Nie pełnej odpowiedzi, ale można uniknąć wielką liczbę trafień, wykonując tę ​​

items= list(items) 
for item in items: 
    spec = getattr(item, item.get_my_specific_type()) 

zamiast tego:

for item in items: 
    spec = getattr(item, item.get_my_specific_type()) 

Rzeczywiście, zmuszając oddanych do listy Pythona, ty Wymuś na Django Orm załadowanie wszystkich elementów w zapytaniu. Następnie robi to w jednym zapytaniu.

+0

To dobra wskazówka (wspomniana w dokumentacji, którą przeczytałem :). I nie jest to zbyt oczywiste. – shylent

+3

Um, to nie jest prawda. Przesyłanie do listy nie ma znaczenia w tym przypadku. W obu wersjach tylko jedna kwerenda jest wykonywana względem tabeli ogólnej, aw obu wersjach jedno zapytanie jest wykonywane względem tabeli SpecificX dla każdego elementu. Tę samą liczbę zapytań dla obu. –