2012-11-29 15 views
5

I mają następujące modele:Zapobieganie O (N) przesyła intermedyacijne modeli

class Artist(models.Model): 
    name = models.CharField() 

    def primary_group(self): 
     return self.memberships.select_related('group').get(is_primary=True) 

class Group(models.Model): 
    name = models.CharField() 
    members = models.ManyToManyField(Artist, through='Membership') 

class Membership(models.Model): 
    artist = models.ForeignKey(Artist, related_name='memberships') 
    group = models.ForeignKey(Group) 
    is_primary = models.BooleanField() 

Artist i Group są połączone za pośrednictwem pośredniego, Membership modelu. Artyści mogą mieć tylko jedną grupę podstawową, która jest oznaczona przez is_primary, zatwierdzona itp.

W szablonie, w którym wymieniam wykonawców, wyświetlam podstawowe informacje o wykonawcy oprócz ich grupy podstawowej, wywoływane za pomocą powyższej metody. Jest to jednak operacja O (n) i mam do tego około 160 artystów. SQL że django-debug-pasek narzędzi zapewnia się następująco:

SELECT ••• FROM "people_membership" 
      LEFT OUTER JOIN "people_group" ON ("people_membership"."group_id" = "people_group"."id") 
      WHERE ("people_membership"."artist_id" = xx AND "people_membership"."is_primary" = true) 

Dodam, że to się dzieje dla każdego artysty wymienione, więc dostać około 160 z nich.

Czy O (n) najlepiej można zrobić, biorąc pod uwagę, że nazywam metodę modelową? Czy jest coś jeszcze, co mogę zrobić, aby to poprawić (brak denormalizacji primary_group)? Wydaje się, że jest to problem z jakimikolwiek informacjami przechowywanymi w pośredniczącym modelu, które chciałbym wywołać ze źródła lub celu.

Odpowiedz

6

Można to łatwo zrobić z dwoma zapytaniami, które pomimo tego, co powie jakieś nienawidzą, nie ma znaczenia w ogóle:

artists = list(Artist.objects.all()) 
primary_memberships = {m.artist_id: m for m in Group.objects.filter(is_primary=True, membership__artist__in=artists).extra(select={'artist_id': '%s.artist_id' % (Membership._meta.db_table,)})} 
for artist in artists: 
    artist.primary_membership = primary_memberships.get(artist.id) 

(Dodatkowa klauzula nie może być poprawne, ale masz pomysł)

Poza tym, chciałbym zmienić funkcję podstawową zrobić tak:

if hasattr(self, '_primary_membership_cache'): 
    return self._primary_membership_cache 

a następnie, jeśli dołączyć informacje, powiązać go z tej zmiennej, a po prostu użyć funkcji swój samego CA ll.

(Śledzimy tego rodzaju wzorca w każdym miejscu na Disqus na różne przyłącza/nieparzyste zapytań)

+1

Jestem pewien, że obecnie jest to w większości poprawne :) –

0

Czy próbowali uruchomieniem kwerendy z członkostwa, a nie artysty?

class Artist(models.Model): 
    ... 
    def primary_group(self): 
     return Membership.objects.filter(artist=self).get(is_primary=True).group 
+0

Wynikowe zapytanie jest prawie takie samo. –

4

zrobiłbym to jak David Cramer mówi, ale zamiast dodatkowo:

primary_memberships = {m.artist_id: m.group for m in Membership.objects.filter(group__isprimary=True, artist__in=artists).select_related('group')} 
for artist in artists: 
    artists.primary_membership = primary_memberships.get(artist.id) 

Dla punktów bonusowych zrobić to metoda na kierownika członkostwa dzięki czemu można stosować go do dowolnej listy artystów łatwo!

1

Co powiesz na umieszczenie dwukolumnowego indeksu na membership (artist_id, is_primary)? If you've already upgraded to 1.5b1 możesz to zrobić w swoich modelach, ale nic nie powstrzyma cię od zrobienia tego na zapleczu, jeśli tego nie zrobiłeś. To powinno zredukować wyszukiwanie członkostwa do stałego czasu. Jeśli twój DB obsługuje to, możesz zrobić to jako partial index, ale z tylko 160 artystami, które nie wydają się wszystkim, co konieczne.

Powiązane problemy