Wie finde ich die Vereinigung zweier Django-Abfragesätze?

84

Ich habe ein Django-Modell mit zwei benutzerdefinierten Manager-Methoden. Jedes gibt eine andere Teilmenge der Objekte des Modells zurück, basierend auf einer anderen Eigenschaft des Objekts.

Gibt es eine Möglichkeit, ein Abfrageset oder nur eine Liste von Objekten abzurufen, dh die Vereinigung der von jeder Manager-Methode zurückgegebenen Abfragesätze?

Paul D. Waite
quelle
3
(Aus einer gelöschten Antwort) In dieser Frage finden Sie eine Variante, die mit QuerySets aus verschiedenen Modellen funktioniert
rnevius
1
Ab Version 1.11 verfügen Django-Abfragesätze über eine integrierte Vereinigungsmethode. Ich habe es als Antwort für zukünftige Referenz hinzugefügt
Jose Cherian

Antworten:

174

Das funktioniert und sieht etwas sauberer aus:

records = query1 | query2

Wenn Sie keine Duplikate wollen, dann müssen Sie anfügen .distinct():

records = (query1 | query2).distinct()
Jordan Reiter
quelle
5
Während die akzeptierte Antwort eine iterierbare Vereinigung zurückgibt (Liste um genau zu sein), wie OP gefragt hat, gibt diese Methode eine echte Vereinigung von Abfragesätzen zurück. Dieser Abfragesatz kann weiter bearbeitet werden, was unter vielen Umständen erwünscht ist.
Krystian Cybulski
5
Aufgrund eines Django-Fehlers kann diese Konstruktion beim Umgang mit ManyToManyFields manchmal zu falschen Ergebnissen führen . Zum Beispiel werden Sie manchmal sehen, dass records.count()dies größer als ist query1.count() + query2.count(), was eindeutig falsch ist.
Jian
4
@Jian Kannst du die Django-Version mit dem Fehler und einem Link zum Djangoprojekt-Problem klären?
IMFletcher
10
records = query1 | query2; records = records.distinct () würde mir das richtige Ergebnis geben
Eugene
4
Sie können Operatoren in Python überladen. Siehe docs.python.org/2/library/operator.html . Django erstellt also spezielle Methoden für das QuerySet-Objekt. Den Code finden Sie hier: github.com/django/django/blob/master/django/db/models/… Die QuerySetKlasse bietet Methoden für __and__und __or__die aufgerufen werden, wenn die Operatoren &oder |zwischen zwei QuerySetObjekten verwendet werden (auch für die QKlasse verwendet) ).
Jordan Reiter
45

Ab Version 1.11 verfügen Django-Abfragesätze über eine integrierte Vereinigungsmethode.

q = q1.union(q2) #q will contain all unique records of q1 + q2
q = q1.union(q2, all=True) #q will contain all records of q1 + q2 including duplicates
q = q1.union(q2,q3) # more than 2 queryset union

Weitere Beispiele finden Sie in meinem Blogbeitrag dazu.

Jose Cherian
quelle
Ich konnte nicht alles = True zum Arbeiten bringen. Am Ende habe ich mein Abfrageset in ein Set umgewandelt, bevor ich es an den Client zurückgegeben habe.
Braden Holt
1
@BradenHolt, all = True, bedeutet, dass es doppelte Datensätze enthält. Sie können einfach all = True entfernen, um zu vermeiden, dass es in eine Menge umgewandelt wird.
Jose Cherian
Nachdem dies nicht funktioniert DjangoFilterBackend, wie kann ich Union und DjangoFilterBackend verwenden?
Nesalexy
Leider scheint dies bei Modellen mit einer in der Meta des Modells definierten Standardreihenfolge nicht zu funktionieren. Immer wenn ich versuche, diese mit .union zu kombinieren, erhalte ich die folgende Fehlermeldung: "ORDER BY ist in Unterabfragen von zusammengesetzten Anweisungen nicht zulässig."
jrial
4

Ich würde vorschlagen, 'query1.union (query2)' anstelle von 'query1 | zu verwenden query2 '; Ich habe unterschiedliche Ergebnisse mit den beiden oben genannten Methoden erzielt, und die erste ist das, was ich erwartet habe. Folgendes war mir begegnet:

print "union result:"
for element in query_set1.union(query_set2):
    print element

print "| result:"
for element in (query_set1 | query_set2):
    print element

Ergebnis:

union result:
KafkaTopic object
KafkaTopic object
KafkaTopic object
KafkaTopic object
KafkaTopic object

| result:
KafkaTopic object
KafkaTopic object
Xianxing
quelle
1
Bitte fügen Sie Code ein, keine Bilder von Code. Der Text in Bildern kann nicht durchsucht werden. Sie können ihn nicht zur Überprüfung in Ihren Editor kopieren / einfügen und benötigen mehr Platz als erforderlich. Verwenden Sie Backticks, um Code als Code zu markieren, damit er korrekt formatiert wird. Siehe den Link "Hilfe" neben dem Texteingabefeld.
jrial
Vielen Dank für die Aktualisierung. :)
jrial