różnica między filtrem z wieloma argumentami a filtrem łańcuchowym w django

Odpowiedzi:

60

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ład 1 (ta sama połączona tabela): 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)

Spowoduje to wyświetlenie wszystkich blogów, które mają jeden wpis z obydwoma (entry__headline__contains='Lennon') AND (entry__pub_date__year=2008), czego można oczekiwać od tego zapytania.

Wynik:

Blog with {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')filtrami wyników, a następnie z nich (entry__pub_date__year=2008).

Różnica polega na tym, że da Ci również wyniki takie jak:

Pojedynczy blog z wieloma wpisami

{entry.headline: '**Lennon**', entry.pub_date: 2000}, 
{entry.headline: 'Bill', entry.pub_date: **2008**}

Podczas oceny pierwszego filtru książka jest uwzględniana ze względu na pierwszy wpis (nawet jeśli zawiera inne pozycje, które nie są zgodne). Gdy oceniany jest drugi filtr, książka jest uwzględniana z powodu drugiego wpisu.

Jedna tabela: ale jeśli zapytanie nie obejmuje tabel połączonych, jak w przykładzie z Yuji i DTing. Wynik jest taki sam.

Johnny Tsang
źródło
21
Zakładam, że dziś rano jestem po prostu gęsty, ale to zdanie mnie wprawia w zakłopotanie: „Ponieważ najpierw filtruje wszystkie blogi z (entry__headline__contains='Lennon')filtrami wyników, a potem z filtrów wyników (entry__pub_date__year=2008)”, jeśli „to z wyniku” jest trafne, dlaczego ma zawierać coś z entry.headline == 'Bill'… . nie entry__headline__contains='Lennon'odfiltrowałby Billinstancji?
Dustin Wyatt,
7
Ja też jestem zdezorientowany. Wygląda na to, że ta odpowiedź jest po prostu błędna, ale ma 37 głosów pozytywnych ...
Personman
1
Ta odpowiedź jest myląca i myląca, zauważ, że powyższe jest poprawne tylko w przypadku filtrowania za pomocą relacji M2M, jak wspomniano w odpowiedzi Yuji. Kluczową kwestią jest to, że przykład filtruje elementy bloga za pomocą każdej instrukcji filtru, a nie elementów Entry.
ogłaszający
1
Ponieważ prawdopodobnie istnieje wiele wpisów na blogu. Język jest poprawny. Koncepcja może być myląca, jeśli nie będziesz pamiętać o wszystkich ruchomych elementach.
Dylan, młody
@DustinWyatt Ja też miałem takie same pytania jak Ty, ale w końcu je dostałem! Zapoznaj się z przykładem pracownika i osoby zależnej napisanej przez Grijesh Chauhan poniżej na tej stronie, a także go otrzymasz.
theQuestionMan
33

Przypadek, w którym wyniki „zapytania filtrującego z wieloma argumentami” różnią się od wyników zapytania „zapytanie filtru łańcuchowego”, a więc:

Wybieranie obiektów, do których istnieją odniesienia, na podstawie obiektów i relacji, do których istnieją odniesienia, to jeden do wielu (lub wiele do wielu).

Wiele filtrów:

    Referenced.filter(referencing1_a=x, referencing1_b=y)
    #  same referencing model   ^^                ^^

Połączone filtry:

    Referenced.filter(referencing1_a=x).filter(referencing1_b=y)

Oba zapytania mogą dawać różne wyniki:
Jeśli więcej niż jeden Referencing1wiersz w modelu odniesienia może odnosić się do tego samego wiersza w modelu odniesienia Referenced. Może to mieć miejsce w przypadku Referenced: Referencing1mają albo 1: N (jeden do wielu) albo N: M (wiele do wielu).

Przykład:

Rozważmy, że moja aplikacja my_companyma dwa modele Employeei Dependent. Pracownik my_companymoże mieć więcej niż osoby pozostające na utrzymaniu (innymi słowy, na utrzymaniu może być syn / córka jednego pracownika, podczas gdy pracownik może mieć więcej niż jednego syna / córkę).
Ech, zakładając, że jak mąż-żona oboje nie mogą pracować w my_company. Wziąłem przykład 1: m

Jest to więc Employeemodel odniesienia, do którego może się odwoływać więcej niż Dependentmodel odniesienia. Rozważmy teraz stan relacji w następujący sposób:

Employee:        Dependent:
+------+        +------+--------+-------------+--------------+
| name |        | name | E-name | school_mark | college_mark |
+------+        +------+--------+-------------+--------------+
| A    |        | a1   |   A    |          79 |           81 |
| B    |        | b1   |   B    |          80 |           60 |
+------+        | b2   |   B    |          68 |           86 |
                +------+--------+-------------+--------------+  

Zależny a1odnosi się do pracownika A, a b1, b2odniesienia zależne do pracownika B.

Teraz moje zapytanie to:

Czy znaleźć wszystkich pracowników, którzy mają syna / córkę, mają wyróżnienia (powiedzmy> = 75%) na uczelni i w szkole?

>>> Employee.objects.filter(dependent__school_mark__gte=75,
...                         dependent__college_mark__gte=75)

[<Employee: A>]

Wynik to „A” na utrzymaniu „a1” ma wyróżnienia zarówno na uczelni, jak i szkoła jest zależna od pracownika „A”. Uwaga „B” nie została wybrana, ponieważ dziecko „B” nie ma wyróżnień zarówno na uczelni, jak iw szkole. Algebra relacyjna:

Pracownik (school_mark> = 75 AND college_mark> = 75) Zależny

Po drugie, potrzebuję zapytania:

Znaleźć wszystkich pracowników, których niektóre osoby pozostające na ich utrzymaniu mają wyróżnienia na uczelni lub w szkole?

>>> Employee.objects.filter(
...             dependent__school_mark__gte=75
...                ).filter(
...             dependent__college_mark__gte=75)

[<Employee: A>, <Employee: B>]

Tym razem „B” również zostało wybrane, ponieważ „B” ma dwoje dzieci (więcej niż jedno!), Jedno ma wyróżnienie w szkole „b1”, a drugie ma wyróżnienie w szkole „b2”.
Kolejność filtrów nie ma znaczenia, powyższe zapytanie możemy również zapisać jako:

>>> Employee.objects.filter(
...             dependent__college_mark__gte=75
...                ).filter(
...             dependent__school_mark__gte=75)

[<Employee: A>, <Employee: B>]

wynik jest taki sam! Algebra relacyjna może być:

(Pracownik (school_mark> = 75) Zależny) (college_mark> = 75) Zależny

Uwaga:

dq1 = Dependent.objects.filter(college_mark__gte=75, school_mark__gte=75)
dq2 = Dependent.objects.filter(college_mark__gte=75).filter(school_mark__gte=75)

Daje ten sam wynik: [<Dependent: a1>]

Sprawdzam docelowe zapytanie SQL wygenerowane przez Django przy użyciu print qd1.queryi print qd2.queryoba są takie same (Django 1.6).

Ale semantycznie oba są dla mnie różne . najpierw wygląda jak prosta sekcja σ [school_mark> = 75 AND college_mark> = 75] (zależna), a druga jak powolne zagnieżdżone zapytanie: σ [school_mark> = 75][college_mark> = 75] (zależne)).

Jeśli potrzebujesz Code @codepad

btw, jest to podane w dokumentacji @ Spanning relacji wielowartościowych Właśnie dodałem przykład, myślę, że będzie pomocny dla kogoś nowego.

Grijesh Chauhan
źródło
4
Dziękuję za to pomocne wyjaśnienie, jest lepsze niż to w dokumentacji, która wcale nie jest jasna.
wim
1
Ostatnia uwaga dotycząca bezpośredniego filtrowania elementów zależnych jest bardzo pomocna. Pokazuje, że zmiana wyników ostatecznie następuje tylko wtedy, gdy przechodzisz przez relację wiele do wielu. Jeśli bezpośrednio odpytujesz tabelę, tworzenie łańcuchów filtrów przypomina dwukrotne czesanie.
Chris
20

W większości przypadków dla zapytania istnieje tylko jeden możliwy zestaw wyników.

Zastosowanie do łączenia filtrów pojawia się, gdy masz do czynienia z m2m:

Rozważ to:

# will return all Model with m2m field 1
Model.objects.filter(m2m_field=1) 

# will return Model with both 1 AND 2    
Model.objects.filter(m2m_field=1).filter(m2m_field=2) 

# this will NOT work
Model.objects.filter(Q(m2m_field=1) & Q(m2m_field=2))

Inne przykłady są mile widziane.

Yuji „Tomita” Tomita
źródło
4
Inny przykład: nie ogranicza się to tylko do m2m, może się to również zdarzyć w przypadku jednego do wielu - z odwrotnym wyszukiwaniem, np. Używając powiązanej_nazwy na kluczu
zagranicznym
Dziękuję za wyjaśnienie! Wcześniej myślałem, że ostatni i drugi przykład są sobie równe, więc ostatni przykład nie zadziałał (złe wyniki zapytania) i spędziłem dużo czasu na wyszukiwaniu. Drugi przykład jest dla mnie bardzo pomocny. Jak powiedział Wim, jest to przydatne w przypadku odwróconych relacji jeden do wielu, jak w moim przypadku.
zen11625
12

Różnica w wydajności jest ogromna. Spróbuj i zobacz.

Model.objects.filter(condition_a).filter(condition_b).filter(condition_c)

jest zaskakująco wolny w porównaniu z

Model.objects.filter(condition_a, condition_b, condition_c)

Jak wspomniano w Effective Django ORM ,

  • QuerySets zachowuje stan w pamięci
  • Łańcuch wyzwala klonowanie, powielanie tego stanu
  • Niestety, QuerySets zachowuje wiele stanów
  • Jeśli to możliwe, nie łącz w łańcuch więcej niż jednego filtra
smoła
źródło
8

Możesz użyć modułu połączenia, aby zobaczyć nieprzetworzone zapytania sql do porównania. Jak wyjaśnił Yuji, w większości są one równoważne, jak pokazano tutaj:

>>> from django.db import connection
>>> samples1 = Unit.objects.filter(color="orange", volume=None)
>>> samples2 = Unit.objects.filter(color="orange").filter(volume=None)
>>> list(samples1)
[]
>>> list(samples2)
[]
>>> for q in connection.queries:
...     print q['sql']
... 
SELECT `samples_unit`.`id`, `samples_unit`.`color`, `samples_unit`.`volume` FROM `samples_unit` WHERE (`samples_unit`.`color` = orange  AND `samples_unit`.`volume` IS NULL)
SELECT `samples_unit`.`id`, `samples_unit`.`color`, `samples_unit`.`volume` FROM `samples_unit` WHERE (`samples_unit`.`color` = orange  AND `samples_unit`.`volume` IS NULL)
>>> 
dting
źródło
3

Ta odpowiedź jest oparta na Django 3.1.

Środowisko

Modele

class Blog(models.Model):
    blog_id = models.CharField()

class Post(models.Model):
    blog_id  = models.ForeignKeyField(Blog)
    title    = models.CharField()
    pub_year = models.CharField() # Don't use CharField for date in production =]

Tabele bazy danych

wprowadź opis obrazu tutaj

Filtry dzwonią

Blog.objects.filter(post__title="Title A", post__pub_year="2020")
# Result: <QuerySet [<Blog: 1>]>

Blog.objects.filter(post__title="Title A").filter(post_pub_date="2020)
# Result: <QuerySet [<Blog: 1>, [<Blog: 2>]>

Wyjaśnienie

Zanim zacznę cokolwiek dalej, muszę zauważyć, że ta odpowiedź opiera się na sytuacji, w której do filtrowania obiektów używa się „ManyToManyField” lub odwrotnego „ForeignKey”.

Jeśli używasz tej samej tabeli lub „OneToOneField” do filtrowania obiektów, nie będzie różnicy między używaniem „Filtra wielu argumentów” lub „Łańcucha filtrów”. Oba będą działać jak filtr warunku „AND”.

Prostym sposobem na zrozumienie, jak używać „Filtra wielu argumentów” i „Łańcucha filtrów”, jest zapamiętanie w filtrze „ManyToManyField” lub odwrotnym filtrze „ForeignKey”, „Filtr wielu argumentów” to warunek „AND”, a „Filter” -chain ”jest warunkiem„ LUB ”.

Powodem, dla którego „Filtr wielu argumentów” i „Łańcuch filtrów” są tak różne, jest to, że pobierają one wynik z innej tabeli łączenia i używają innego warunku w instrukcji zapytania.

„Filtr wielu argumentów” używa słowa „Opublikuj”. „Rok_publiczny” = „2020” do określenia roku publicznego

SELECT *
FROM "Book" 
INNER JOIN ("Post" ON "Book"."id" = "Post"."book_id")
WHERE "Post"."Title" = 'Title A'
AND "Post"."Public_Year" = '2020'

Zapytanie do bazy danych „łańcuch filtrów” używa „T1”. „Public_Year” = „2020” do określenia roku publicznego

SELECT *
FROM "Book" 
INNER JOIN "Post" ON ("Book"."id" = "Post"."book_id")
INNER JOIN "Post" T1 ON ("Book"."id" = "T1"."book_id")
WHERE "Post"."Title" = 'Title A'  
AND "T1"."Public_Year" = '2020'

Ale dlaczego różne warunki wpływają na wynik?

Wydaje mi się, że większość z nas, którzy przychodzą na tę stronę, w tym ja =], ma to samo założenie, używając na początku „filtru wielu argumentów” i „łańcucha filtrów”.

Który naszym zdaniem wynik powinien zostać pobrany z tabeli, takiej jak następująca, która jest poprawna dla „Filtra wielu argumentów”. Jeśli więc używasz „Filtra wielu argumentów”, otrzymasz wynik zgodny z oczekiwaniami.

wprowadź opis obrazu tutaj

Ale podczas zajmowania się „łańcuchem filtrów” Django tworzy inną instrukcję zapytania, która zmienia powyższą tabelę na następną. Ponadto „Rok publiczny” jest identyfikowany w sekcji „T1” zamiast w sekcji „Opublikuj” z powodu zmiany wyrażenia zapytania.

wprowadź opis obrazu tutaj

Ale skąd pochodzi ten dziwny diagram tabeli łączenia filtrów z łańcuchem filtrów?

Nie jestem ekspertem od baz danych. Poniższe wyjaśnienie jest tym, co rozumiem do tej pory po utworzeniu tej samej struktury bazy danych i wykonaniu testu z tą samą instrukcją zapytania.

Poniższy diagram pokaże, skąd wziął się ten dziwny diagram tabeli łączenia łańcuchów filtrów.

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

Baza danych najpierw utworzy tabelę łączenia, dopasowując kolejno wiersze tabel „Blog” i „Post”.

Następnie baza danych ponownie wykonuje ten sam proces dopasowywania, ale wykorzystuje tabelę wyników kroku 1 do dopasowania tabeli „T1”, która jest po prostu tą samą tabelą „Post”.

I stąd wziął się ten dziwny diagram tabeli złączeń typu „łańcuch filtrów”.

Wniosek

Dwie rzeczy sprawiają, że „Filtr wielu argumentów” i „Łańcuch filtrów” są różne.

  1. Django tworzy różne instrukcje zapytań dla „Filtra wielu argumentów” i „Łańcucha filtrów”, co powoduje, że wyniki „Filtra wielu argumentów” i „Łańcuch filtrów” pochodzą z różnych tabel.
  2. Instrukcja zapytania „Łańcuch filtrów” identyfikuje warunek z innego miejsca niż „Filtr wielu argumentów”.

Brudnym sposobem na zapamiętanie, jak go używać, jest to, że „Filtr wielu argumentów” to warunek „AND” , a „łańcuch filtrów” to warunek „LUB” , gdy znajduje się w filtrze „ManyToManyField” lub odwrotnym filtrze „ForeignKey”.

LearnerAndLearn
źródło
2

Jeśli znajdziesz się na tej stronie, szukając sposobu dynamicznego tworzenia zestawu zapytań django z wieloma filtrami łańcuchowymi, ale potrzebujesz filtrów tego ANDtypu zamiast OR, rozważ użycie obiektów Q .

Przykład:

# First filter by type.
filters = None
if param in CARS:
  objects = app.models.Car.objects
  filters = Q(tire=param)
elif param in PLANES:
  objects = app.models.Plane.objects
  filters = Q(wing=param)

# Now filter by location.
if location == 'France':
  filters = filters & Q(quay=location)
elif location == 'England':
  filters = filters & Q(harbor=location)

# Finally, generate the actual queryset
queryset = objects.filter(filters)
Matt
źródło
W przypadku nieprzekazania if lub elif, zmienna filter będzie miała wartość None, a następnie otrzymasz TypeError: unsupported operand type (s) for &: 'NoneType' i 'Q'. Zainicjowałem filtry z filtrami = Q ()
cwhisperer
-4

Na przykład istnieje różnica, gdy masz żądanie dotyczące powiązanego obiektu

class Book(models.Model):
    author = models.ForeignKey(Author)
    name = models.ForeignKey(Region)

class Author(models.Model):
    name = models.ForeignKey(Region)

żądanie

Author.objects.filter(book_name='name1',book_name='name2')

zwraca pusty zestaw

i prośba

Author.objects.filter(book_name='name1').filter(book_name='name2')

zwraca autorów, którzy mają książki z „imieniem1” i „imieniem2”

po szczegóły zajrzyj na https://docs.djangoproject.com/en/dev/topics/db/queries/#s-spanning-multi-valued-relationships

Kuna
źródło
5
Author.objects.filter(book_name='name1',book_name='name2')nie jest nawet poprawnym Pythonem, byłoby toSyntaxError: keyword argument repeated
wim
1
Gdzie dokładnie zdefiniowano nazwę_książki? Czy masz na myśli book_set__name?
DylanYoung