Zawsze zakładałem, że łączenie w łańcuch wielu wywołań filter () w Django jest zawsze tym samym, co zbieranie ich w jednym wywołaniu.
# Equivalent
Model.objects.filter(foo=1).filter(bar=2)
Model.objects.filter(foo=1,bar=2)
ale natknąłem się na skomplikowany zestaw zapytań w moim kodzie, gdzie tak nie jest
class Inventory(models.Model):
book = models.ForeignKey(Book)
class Profile(models.Model):
user = models.OneToOneField(auth.models.User)
vacation = models.BooleanField()
country = models.CharField(max_length=30)
# Not Equivalent!
Book.objects.filter(inventory__user__profile__vacation=False).filter(inventory__user__profile__country='BR')
Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')
Wygenerowany kod SQL to
SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") INNER JOIN "library_inventory" T5 ON ("library_book"."id" = T5."book_id") INNER JOIN "auth_user" T6 ON (T5."user_id" = T6."id") INNER JOIN "library_profile" T7 ON (T6."id" = T7."user_id") WHERE ("library_profile"."vacation" = False AND T7."country" = BR )
SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") WHERE ("library_profile"."vacation" = False AND "library_profile"."country" = BR )
Pierwszy zestaw zapytań z połączonymi filter()
wywołaniami dwukrotnie łączy się z modelem Inventory, tworząc OR między dwoma warunkami, podczas gdy drugi zestaw zapytań ORAZ te dwa warunki razem. Spodziewałem się, że pierwsze zapytanie będzie również ORAZ dwoma warunkami. Czy jest to oczekiwane zachowanie, czy jest to błąd w Django?
Odpowiedź na pokrewne pytanie Czy używanie funkcji „.filter (). Filter (). Filter () ...” w Django ma wady? wydaje się wskazywać, że dwa zestawy zapytań powinny być równoważne.
źródło
further restrict
znaczyless restrictive
?Te dwa style filtrowania są w większości przypadków równoważne, ale podczas wykonywania zapytań dotyczących obiektów opartych na ForeignKey lub ManyToManyField są nieco inne.
Przykłady z dokumentacji .
Model
Blog to Entry to relacja jeden do wielu.
from django.db import models class Blog(models.Model): ... class Entry(models.Model): blog = models.ForeignKey(Blog) headline = models.CharField(max_length=255) pub_date = models.DateField() ...
obiekty
Zakładając, że są tutaj obiekty blogów i wpisów.
zapytania
Blog.objects.filter(entry__headline_contains='Lennon', entry__pub_date__year=2008) Blog.objects.filter(entry__headline_contains='Lennon').filter( entry__pub_date__year=2008)
W przypadku pierwszego zapytania (jedno filtru) pasuje tylko do blog1.
W przypadku drugiego zapytania (jedno z filtrami łańcuchowymi) odfiltrowuje blog1 i blog2.
Pierwszy filtr ogranicza zestaw zapytań do blog1, blog2 i blog5; drugi filtr ogranicza zbiór blogów dalej do blog1 i blog2.
Powinniście to sobie uświadomić
Więc to nie to samo, ponieważ Blog i Wpis są relacjami o wielu wartościach.
Źródła: https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships
Jeśli coś jest nie tak, proszę mnie poprawić.
Edycja: Zmieniono wersję 1.6 na 1.8, ponieważ linki 1.6 nie są już dostępne.
źródło
Jak widać w wygenerowanych instrukcjach SQL, różnica nie polega na „LUB”, jak niektórzy mogą podejrzewać. Tak jest umieszczane GDZIE i DOŁĄCZ.
Przykład1 (ta sama połączona tabela):
(przykład z https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships )
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
To da ci wszystkie blogi, które mają jeden wpis z obydwoma (entry_ headline _contains = 'Lennon') ORAZ (entry__pub_date__year = 2008), czego można się spodziewać po tym zapytaniu. Wynik: Książka z {entry.headline: „Life of Lennon”, entry.pub_date: „2008”}
Przykład 2 (przykuty)
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
To obejmie wszystkie wyniki z przykładu 1, ale wygeneruje nieco więcej wyników. Ponieważ najpierw filtruje wszystkie blogi z (entry_ headline _contains = 'Lennon'), a następnie z filtrów wyników (entry__pub_date__year = 2008).
Różnica polega na tym, że daje to również wyniki takie jak: Książka z {entry.headline: ' Lennon ', entry.pub_date: 2000}, {entry.headline: 'Bill', entry.pub_date: 2008 }
W Twoim przypadku
Myślę, że to ten, którego potrzebujesz:
Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')
A jeśli chcesz użyć OR, przeczytaj: https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects
źródło
Czasami nie chcesz łączyć wielu filtrów w ten sposób:
def your_dynamic_query_generator(self, event: Event): qs \ .filter(shiftregistrations__event=event) \ .filter(shiftregistrations__shifts=False)
A poniższy kod w rzeczywistości nie zwróciłby właściwej rzeczy.
def your_dynamic_query_generator(self, event: Event): return Q(shiftregistrations__event=event) & Q(shiftregistrations__shifts=False)
Teraz możesz użyć filtru zliczającego adnotacje.
W tym przypadku liczymy wszystkie przesunięcia, które należą do określonego zdarzenia.
qs: EventQuerySet = qs.annotate( num_shifts=Count('shiftregistrations__shifts', filter=Q(shiftregistrations__event=event)) )
Następnie możesz filtrować według adnotacji.
def your_dynamic_query_generator(self): return Q(num_shifts=0)
To rozwiązanie jest również tańsze w przypadku dużych zestawów zapytań.
Mam nadzieję że to pomoże.
źródło