Unterschied zwischen Filter mit mehreren Argumenten und Kettenfilter in Django

Antworten:

60

Wie Sie in den generierten SQL-Anweisungen sehen können, ist der Unterschied nicht das "ODER", wie manche vermuten. So wird WHERE und JOIN platziert.

Beispiel 1 (gleiche verknüpfte Tabelle): von https://docs.djangoproject.com/de/dev/topics/db/queries/#spanning-multi-valued-relationships

Blog.objects.filter(
       entry__headline__contains='Lennon', 
       entry__pub_date__year=2008)

Dadurch erhalten Sie alle Blogs, die einen Eintrag mit beiden haben (entry__headline__contains='Lennon') AND (entry__pub_date__year=2008), was Sie von dieser Abfrage erwarten würden.

Ergebnis:

Blog with {entry.headline: 'Life of Lennon', entry.pub_date: '2008'}

Beispiel 2 (verkettet)

Blog.objects.filter(
       entry__headline__contains='Lennon'
           ).filter(
       entry__pub_date__year=2008)

Dies deckt alle Ergebnisse aus Beispiel 1 ab, erzeugt jedoch etwas mehr Ergebnisse. Weil es zuerst alle Blogs mit (entry__headline__contains='Lennon')und dann aus den Ergebnisfiltern filtert (entry__pub_date__year=2008).

Der Unterschied besteht darin, dass Sie auch folgende Ergebnisse erhalten:

Ein einzelner Blog mit mehreren Einträgen

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

Wenn der erste Filter ausgewertet wurde, ist das Buch aufgrund des ersten Eintrags enthalten (obwohl es andere Einträge enthält, die nicht übereinstimmen). Wenn der zweite Filter ausgewertet wird, wird das Buch aufgrund des zweiten Eintrags aufgenommen.

Eine Tabelle: Wenn die Abfrage jedoch keine verknüpften Tabellen wie das Beispiel von Yuji und DTing umfasst. Das Ergebnis ist das gleiche.

Johnny Tsang
quelle
21
Ich nehme an, ich bin heute Morgen nur dicht, aber dieser Satz verwirrt mich: "Weil er zuerst alle Blogs mit (entry__headline__contains='Lennon')und dann aus den Ergebnisfiltern filtert (entry__pub_date__year=2008)" Wenn "dann aus dem Ergebnis" korrekt ist, warum wird er etwas mit ... enthalten entry.headline == 'Bill'? .wollte entry__headline__contains='Lennon'die BillInstanz nicht herausfiltern ?
Dustin Wyatt
7
Ich bin auch verwirrt. Es scheint, als ob diese Antwort einfach falsch ist, aber sie hat 37 positive Stimmen ...
Personman
1
Diese Antwort ist irreführend und verwirrend. Beachten Sie, dass die obigen Angaben nur dann korrekt sind, wenn Sie mithilfe von M2M-Beziehungen filtern, wie in Yujis Antwort angegeben. Der entscheidende Punkt ist, dass das Beispiel das Filtern der Blog-Elemente mit jeder Filteranweisung und nicht der Eintragselemente ist.
Ansager
1
Weil es möglicherweise mehrere Einträge pro Blog gibt. Die Sprache ist korrekt. Das Konzept kann verwirrend sein, wenn Sie nicht alle beweglichen Teile im Auge behalten.
DylanYoung
@DustinWyatt Ich hatte auch die gleichen Fragen wie du, aber ich habe es endlich verstanden! Bitte lesen Sie das Beispiel für Mitarbeiter und abhängige Personen, das von Grijesh Chauhan unten auf dieser Seite geschrieben wurde, und Sie werden es auch erhalten.
theQuestionMan
33

Der Fall, in dem sich die Ergebnisse von "Filterabfrage mit mehreren Argumenten" von "verketteter Filterabfrage" unterscheiden, ist folgender:

Die Auswahl von referenzierten Objekten auf der Grundlage von referenzierenden Objekten und Beziehungen erfolgt eins zu viele (oder viele zu viele).

Mehrere Filter:

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

Verkettete Filter:

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

Beide Abfragen können unterschiedliche Ergebnisse ausgeben:
Wenn mehr als eine Referencing1Zeile im Referenzierungsmodell auf dieselbe Zeile im Referenzierungsmodell verweisen kannReferenced . Dies kann der Fall sein in Referenced: Referencing1entweder 1: N (eins zu viele) oder N: M (viele zu viele) Beziehung haben.

Beispiel:

Betrachten Sie meine Anwendung my_companyhat zwei Modelle Employeeund Dependent. Ein Mitarbeiter in my_companykann mehr als abhängige Personen haben (mit anderen Worten, ein abhängiger Mitarbeiter kann Sohn / Tochter eines einzelnen Mitarbeiters sein, während ein Mitarbeiter mehr als einen Sohn / eine Tochter haben kann).
Ehh, vorausgesetzt, wie Ehemann-Ehefrau können beide nicht in einem arbeiten my_company. Ich habe 1: m Beispiel genommen

Es Employeehandelt sich also um ein Referenzmodell, auf das von mehr als dem Referenzierungsmodell Dependentverwiesen werden kann. Betrachten Sie nun den Beziehungsstatus wie folgt:

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

Abhängig a1bezieht sich auf Mitarbeiter Aund abhängigb1, b2 Verweise auf Mitarbeiter B.

Jetzt ist meine Frage:

Finden Sie alle Mitarbeiter, deren Sohn / Tochter sowohl im College als auch in der Schule Unterscheidungsmerkmale (sagen wir> = 75%) hat?

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

[<Employee: A>]

Ausgabe ist 'A' abhängig 'a1' hat sowohl im College als auch in der Schule Unterscheidungsmerkmale und ist abhängig von Mitarbeiter 'A'. Hinweis 'B' ist nicht ausgewählt, da keiner der Kinder von 'B' sowohl im College als auch in der Schule Unterscheidungsmerkmale aufweist. Relationale Algebra:

Mitarbeiter (Schulzeichen> = 75 UND Hochschulzeichen> = 75) Abhängig

Im zweiten Fall benötige ich eine Abfrage:

Finden Sie alle Mitarbeiter, deren Angehörige im College und in der Schule Unterscheidungsmerkmale haben?

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

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

Dieses Mal wurde 'B' auch ausgewählt, weil 'B' zwei Kinder hat (mehr als eines!), Eines in der Schule 'b1' und das andere im College 'b2' ein Unterscheidungszeichen hat.
Die Reihenfolge des Filters spielt keine Rolle. Wir können die obige Abfrage auch wie folgt schreiben:

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

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

Ergebnis ist das gleiche! Relationale Algebra kann sein:

(Mitarbeiter (Schulzeichen> = 75) Abhängig) (Hochschulzeichen> = 75) Abhängig

Beachten Sie Folgendes:

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)

Gibt das gleiche Ergebnis aus: [<Dependent: a1>]

Ich überprüfe die von Django generierte SQL-Zielabfrage mit print qd1.queryund print qd2.querybeide sind gleich (Django 1.6).

Aber semantisch unterscheiden sich beide von mir . Erstens sieht es aus wie ein einfacher Abschnitt σ [school_mark> = 75 AND college_mark> = 75] (abhängig) und zweitens wie eine langsam verschachtelte Abfrage: σ [school_mark> = 75][college_mark> = 75] (abhängig)).

Wenn man Code @codepad braucht

Übrigens ist es in der Dokumentation @ Spanning mehrwertiger Beziehungen angegeben. Ich habe gerade ein Beispiel hinzugefügt. Ich denke, es wird für jemanden hilfreich sein, der neu ist.

Grijesh Chauhan
quelle
4
Vielen Dank für diese hilfreiche Erklärung, sie ist besser als die in der Dokumentation, die überhaupt nicht klar ist.
wim
1
Die letzte Markierung zum direkten Filtern der Abhängigen ist sehr hilfreich. Es zeigt, dass die Änderung der Ergebnisse definitiv nur dann erfolgt, wenn Sie eine Viele-zu-Viele-Beziehung durchlaufen. Wenn Sie eine Tabelle direkt abfragen, ist das Verketten von Filtern wie das zweimalige Kämmen.
Chris
20

In den meisten Fällen gibt es nur eine mögliche Ergebnismenge für eine Abfrage.

Die Verwendung für Verkettungsfilter kommt, wenn Sie mit m2m arbeiten:

Bedenken Sie:

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

Andere Beispiele sind willkommen.

Yuji 'Tomita' Tomita
quelle
4
Ein weiteres Beispiel: Es ist nicht nur auf m2m beschränkt, dies kann auch mit Eins-zu-Viele geschehen - mit der umgekehrten Suche, z. B. unter Verwendung des verwandten_Namens auf einem ForeignKey
wim
Danke für Ihre Erklärung! Vorher dachte ich, dass das letzte und das zweite Beispiel gleich sind, so dass das letzte Beispiel für mich nicht funktioniert hat (falsche Abfrageergebnisse), und ich habe viel Zeit mit Suchen verbracht. 2. Beispiel sehr hilfreich für mich. Auch wie Wim sagte, ist dies mit umgekehrten Eins-zu-Viele-Beziehungen wie in meinem Fall verwendbar.
Zen11625
12

Der Leistungsunterschied ist enorm. Probieren Sie es aus und sehen Sie.

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

ist überraschend langsam im Vergleich zu

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

Wie in Effective Django ORM erwähnt ,

  • QuerySets behalten den Status im Speicher bei
  • Das Verketten löst das Klonen aus und dupliziert diesen Status
  • Leider behalten QuerySets viel Status bei
  • Wenn möglich, verketten Sie nicht mehr als einen Filter
Teer
quelle
8

Sie können das Verbindungsmodul verwenden, um die zu vergleichenden SQL-Abfragen anzuzeigen. Wie von Yuji erklärt, sind sie größtenteils gleichwertig, wie hier gezeigt:

>>> 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
quelle
2

Wenn Sie auf dieser Seite nach Möglichkeiten suchen, ein Django-Abfrageset mit mehreren Verkettungsfiltern dynamisch aufzubauen, die Filter jedoch vom ANDTyp sein ORmüssen, sollten Sie Q-Objekte verwenden .

Ein Beispiel:

# 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
quelle
Wenn if oder elif nicht übergeben wird, lautet die Filtervariable None, und Sie erhalten einen TypeError: nicht unterstützte Operandentyp (e) für &: 'NoneType' und 'Q'. Ich habe die Filter mit filter = Q ()
cwhisperer
2

Diese Antwort basiert auf Django 3.1.

Umgebung

Modelle

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 =]

Datenbanktabellen

Geben Sie hier die Bildbeschreibung ein

Filter rufen auf

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>]>

Erläuterung

Bevor ich etwas weiter anfange, muss ich feststellen, dass diese Antwort auf der Situation basiert, in der "ManyToManyField" oder ein umgekehrter "ForeignKey" zum Filtern von Objekten verwendet wird.

Wenn Sie dieselbe Tabelle oder ein "OneToOneField" zum Filtern von Objekten verwenden, gibt es keinen Unterschied zwischen der Verwendung von "Filter für mehrere Argumente" oder "Filterkette". Beide funktionieren wie ein "UND" -Bedingungsfilter.

Der einfache Weg, um zu verstehen, wie "Filter für mehrere Argumente" und "Filterkette" verwendet werden, besteht darin, sich in einem "ManyToManyField" - oder einem umgekehrten "ForeignKey" -Filter zu merken, dass "Filter für mehrere Argumente" eine "UND" -Bedingung und "Filter" ist -chain "ist eine" ODER "-Bedingung.

Der Grund, warum "Filter für mehrere Argumente" und "Filterkette" so unterschiedlich sind, liegt darin, dass sie Ergebnisse aus unterschiedlichen Verknüpfungstabellen abrufen und unterschiedliche Bedingungen in der Abfrageanweisung verwenden.

"Filter für mehrere Argumente" verwendet "Post". "Public_Year" = '2020' , um das öffentliche Jahr zu identifizieren

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

Die Datenbankabfrage "Filterkette" verwendet "T1". "Public_Year" = '2020' , um das öffentliche Jahr zu identifizieren

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'

Aber warum wirken sich unterschiedliche Bedingungen auf das Ergebnis aus?

Ich glaube, die meisten von uns, die auf diese Seite kommen, einschließlich mir =], haben die gleiche Annahme, wenn sie zuerst "Filter für mehrere Argumente" und "Filterkette" verwenden.

Wir glauben, dass das Ergebnis aus einer Tabelle wie der folgenden abgerufen werden sollte, die für "Filter für mehrere Argumente" korrekt ist. Wenn Sie also "Filter für mehrere Argumente" verwenden, erhalten Sie ein Ergebnis wie erwartet.

Geben Sie hier die Bildbeschreibung ein

Während Django sich mit der "Filterkette" befasst, erstellt er eine andere Abfrageanweisung, die die obige Tabelle in die folgende ändert. Außerdem wird das "öffentliche Jahr" aufgrund der Änderung der Abfrageanweisung im Abschnitt "T1" anstelle des Abschnitts "Post" angegeben.

Geben Sie hier die Bildbeschreibung ein

Aber woher kommt dieses seltsame "Filter-Chain" -Verbindungs-Tabellendiagramm?

Ich bin kein Datenbankexperte. Die folgende Erklärung ist das, was ich bisher verstanden habe, nachdem ich dieselbe Struktur der Datenbank erstellt und einen Test mit derselben Abfrageanweisung durchgeführt habe.

Das folgende Diagramm zeigt, wie dieses seltsame "Filter-Chain" -Verbindungs-Tabellendiagramm stammt.

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Die Datenbank erstellt zunächst eine Verknüpfungstabelle, indem die Zeilen der Tabellen "Blog" und "Post" nacheinander abgeglichen werden.

Danach führt die Datenbank nun erneut denselben Abgleich durch, verwendet jedoch die Ergebnistabelle von Schritt 1, um mit der Tabelle "T1" übereinzustimmen, die genau dieselbe "Post" -Tabelle ist.

Und so kommt dieses seltsame "Filter-Chain" -Verbindungs-Tabellendiagramm.

Fazit

Zwei Dinge unterscheiden also "Filter für mehrere Argumente" und "Filterkette".

  1. Django erstellt unterschiedliche Abfrageanweisungen für "Filter für mehrere Argumente" und "Filterkette", wodurch die Ergebnisse für "Filter für mehrere Argumente" und "Filterkette" aus unterschiedlichen Tabellen stammen.
  2. Die Abfrageanweisung "Filterkette" identifiziert die Bedingung von einer anderen Stelle als "Filter für mehrere Argumente".

Die schmutzige Art, sich daran zu erinnern, wie man es benutzt, ist "Multiple Arguments Filter" ist eine "AND" -Bedingung und "Filter-chain" ist eine "OR" -Bedingung, während in einem "ManyToManyField" - oder einem umgekehrten "ForeignKey" -Filter.

LearnerAndLearn
quelle
-4

Es gibt einen Unterschied, wenn Sie beispielsweise eine Anfrage an Ihr verwandtes Objekt haben

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

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

Anfrage

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

Gibt einen leeren Satz zurück

und Anfrage

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

Gibt Autoren zurück, die Bücher mit 'name1' und 'name2' haben.

Weitere Informationen finden Sie unter https://docs.djangoproject.com/de/dev/topics/db/queries/#s-spanning-multi-valued-relationships

Marder
quelle
5
Author.objects.filter(book_name='name1',book_name='name2')ist nicht einmal gültige Python, es wäreSyntaxError: keyword argument repeated
wim
1
Wo ist book_name genau definiert? Meinst du book_set__name?
DylanYoung