Comment puis-je trouver l'union de deux ensembles de requêtes Django?

92

J'ai un modèle Django avec deux méthodes de gestion personnalisées. Chacun renvoie un sous-ensemble différent des objets du modèle, basé sur une propriété différente de l'objet.

Existe-t-il un moyen d'obtenir un ensemble de requêtes, ou simplement une liste d'objets, c'est l'union des ensembles de requêtes retournés par chaque méthode de gestionnaire?

Paul D. Waite
la source
3
(À partir d'une réponse supprimée) Voir cette question pour une variante qui fonctionne avec les QuerySets de différents modèles: stackoverflow.com/questions/431628/…
rnevius
1
À partir de la version 1.11, les ensembles de requêtes django ont une méthode d'union intégrée. Je l'ai ajouté comme réponse pour référence future
Jose Cherian

Réponses:

179

Cela fonctionne et semble un peu plus propre:

records = query1 | query2

Si vous ne voulez pas de doublons, vous devrez ajouter .distinct():

records = (query1 | query2).distinct()
Jordan Reiter
la source
5
Alors que la réponse acceptée renvoie une union itérable (liste pour être exact), comme OP l'a demandé, cette méthode renvoie une véritable union d'ensembles de requêtes. Cet ensemble de requêtes peut être exploité davantage, ce qui est souhaité dans de nombreuses circonstances.
Krystian Cybulski le
5
En raison d'un bogue Django, cette construction peut parfois renvoyer des résultats incorrects lors du traitement de ManyToManyFields. Par exemple, vous verrez parfois que records.count()sera supérieur à query1.count() + query2.count(), ce qui est clairement incorrect.
Jian
4
@Jian pouvez-vous clarifier la version de django avec le bogue et un lien vers le problème de djangoproject?
IMFletcher
10
records = query1 | query2; records = records.distinct () me donnerait le résultat correct
eugene
5
Vous pouvez surcharger les opérateurs en Python. Voir docs.python.org/2/library/operator.html . Ainsi, Django crée des méthodes spéciales pour l'objet QuerySet. Voir le code ici: github.com/django/django/blob/master/django/db/models/… la QuerySetclasse fournit des méthodes pour __and__et __or__qui sont appelées lorsque les opérateurs &ou |sont utilisés entre deux QuerySetobjets (également utilisés pour la Qclasse ).
Jordan Reiter
49

À partir de la version 1.11 , les ensembles de requêtes django ont une méthode d'union intégrée.

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

Voir mon article de blog à ce sujet pour plus d'exemples.

José Cherian
la source
Je ne pouvais pas tout faire fonctionner = True. J'ai fini par convertir mon ensemble de requêtes en un ensemble avant de le renvoyer au client.
Braden Holt
1
@BradenHolt, all = True, signifie qu'il contiendra des enregistrements en double. Vous pouvez simplement supprimer all = True pour éviter de le convertir en un ensemble.
Jose Cherian
après que cela ne fonctionne pas DjangoFilterBackend, comment puis-je utiliser l'union et DjangoFilterBackend?
nesalexy
Malheureusement, cela ne semble pas fonctionner pour les modèles avec un ordre par défaut défini dans la méta du modèle. Chaque fois que j'essaye de les combiner avec .union, je reçois le message d'erreur suivant: "ORDER BY n'est pas autorisé dans les sous-requêtes d'instructions composées."
jrial
4

Je suggérerais d'utiliser «query1.union (query2)» au lieu de «query1 | query2 '; J'ai obtenu des résultats différents des deux méthodes ci-dessus et la première est ce à quoi je m'attendais. Voici ce que j'avais rencontré:

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

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

résultat:

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

| result:
KafkaTopic object
KafkaTopic object
Xianxing
la source
1
Veuillez coller le code, pas des images de code. Le texte des images ne peut pas faire l'objet de recherches, vous ne pouvez pas le copier / coller dans votre éditeur pour vérification et prend plus d'espace que nécessaire. Utilisez des backticks pour marquer le code comme code, afin qu'il soit formaté correctement. Voir le lien «aide» à côté de la zone de saisie de texte.
jrial
Merci pour la mise à jour. :)
jrial