Wie erstelle ich dynamisch einen ODER-Abfragefilter in Django?


104

In einem Beispiel sehen Sie einen Mehrfach-ODER-Abfragefilter:

Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))

Dies führt beispielsweise zu:

[<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>]

Ich möchte diesen Abfragefilter jedoch aus einer Liste erstellen. Wie geht das?

z.B [1, 2, 3] -> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))


1
Sie scheinen dies zweimal gefragt zu haben: stackoverflow.com/questions/852404
Dominic Rodger

Für diesen speziellen Anwendungsfall würden Sie wahrscheinlich Article.objects.filter(pk__in=[1, 2, 3])im modernen Django verwenden, aber die Frage ist immer noch relevant, wenn Sie etwas weiter fortgeschrittenes tun möchten, indem Sie Q-Objekte zusammenfügen.
Berühmte

Antworten:


162

Sie können Ihre Abfragen wie folgt verketten:

values = [1,2,3]

# Turn list of values into list of Q objects
queries = [Q(pk=value) for value in values]

# Take one Q object from the list
query = queries.pop()

# Or the Q object with the ones remaining in the list
for item in queries:
    query |= item

# Query the model
Article.objects.filter(query)

3
Vielen Dank! Das war es, wonach ich gesucht habe :) Ich wusste nicht, dass du es tun kannst | =
Jack Ha

23
Sie können die Abfrage auch initialisieren mit: query = Q ()
chachan

5
Sie können dynamische Felder erstellen, indem Sie ** {'Feldname': Wert}: Abfragen = [Q (** {'Feldname': Wert}) für Wert in Werten] verwenden
Sie den

1
Wie können Sie mit Django Rohabfragen erstellen, wenn Sie optionale Bedingungen wie oben hinzufügen möchten?
Benutzer

Das hat bei mir nicht funktioniert, ich weiß nicht warum. Abfragen geben für mich keine Ergebnisse zurück
Mehran Nouri

83

Um komplexere Abfragen zu erstellen, besteht auch die Möglichkeit, die Konstanten Q.OR und Q.AND des eingebauten Q () -Objekts zusammen mit der add () -Methode wie folgt zu verwenden:

list = [1, 2, 3]
# it gets a bit more complicated if we want to dynamically build
# OR queries with dynamic/unknown db field keys, let's say with a list
# of db fields that can change like the following
# list_with_strings = ['dbfield1', 'dbfield2', 'dbfield3']

# init our q objects variable to use .add() on it
q_objects = Q(id__in=[])

# loop trough the list and create an OR condition for each item
for item in list:
    q_objects.add(Q(pk=item), Q.OR)
    # for our list_with_strings we can do the following
    # q_objects.add(Q(**{item: 1}), Q.OR)

queryset = Article.objects.filter(q_objects)

# sometimes the following is helpful for debugging (returns the SQL statement)
# print queryset.query

12
Für Neulinge in diesem Thread, wie ich, denke ich, dass diese Antwort als die beste Antwort angesehen werden sollte. Es ist mehr Djangoesque als die akzeptierte Antwort. Danke dir!
Theresaanna

4
Ich würde diskutieren, dass es pythonischer ist, die eingebauten Operatoren OR und AND (| und &) zu verwenden. q_objects |= Q(pk=item)
Bobort

Perfekt! Danke dir!
RL Shyam

1
Beachten Sie, dass Sie, wenn listes leer ist, das Äquivalent von zurückgeben Article.objects.all(). Leicht zu entschärfen Article.objects.none(), wenn Sie für diesen Test zurückkehren.
Wil

2
@Wil Sie können auch initialisieren q_objectsmit Q(id__in=[]). Es wird immer fehlschlagen, es sei denn, mit etwas ODER-verknüpft und das Abfrageoptimierungsprogramm wird es gut handhaben.
Jonathan Richards

44

Eine kürzere Art, Dave Webbs Antwort mit der Reduktionsfunktion von Python zu schreiben :

# For Python 3 only
from functools import reduce

values = [1,2,3]

# Turn list of values into one big Q objects  
query = reduce(lambda q,value: q|Q(pk=value), values, Q())  

# Query the model  
Article.objects.filter(query)  

Es sieht so aus, als ob die "eingebaute" Reduzierung entfernt und durch ersetzt wurde functools.reduce. Quelle
lsowen

Danke @lsowen, behoben.
Tom Viner

Und es ist möglich, operator.or_anstelle des Lambda zu verwenden.
Eigenein

38
from functools import reduce
from operator import or_
from django.db.models import Q

values = [1, 2, 3]
query = reduce(or_, (Q(pk=x) for x in values))

Ok, aber woher kommt das operator?
mpiskore

1
@mpiskore: Gleicher Ort wie jedes andere Python-Modul: Sie importieren es.
Ignacio Vazquez-Abrams

1
komisch. das war wirklich meine frage: in welchem ​​modul / in welcher bibliothek kann ich es finden? Google hat nicht viel geholfen.
mpiskore

Oh, ich dachte, es wäre eine Art Django ORM-Operator. Wie dumm von mir, danke!
mpiskore

20

Vielleicht ist es besser, die SQL IN-Anweisung zu verwenden.

Article.objects.filter(id__in=[1, 2, 3])

Siehe Queryset-API-Referenz .

Wenn Sie wirklich Abfragen mit dynamischer Logik durchführen müssen, können Sie Folgendes tun (hässlich + nicht getestet):

query = Q(field=1)
for cond in (2, 3):
    query = query | Q(field=cond)
Article.objects.filter(query)

1
Sie könnten auch verwendenquery |= Q(field=cond)
Bobort

8

Siehe die Dokumente :

>>> Blog.objects.in_bulk([1])
{1: <Blog: Beatles Blog>}
>>> Blog.objects.in_bulk([1, 2])
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
>>> Blog.objects.in_bulk([])
{}

Beachten Sie, dass diese Methode nur für die Suche nach Primärschlüsseln funktioniert. Dies scheint jedoch das zu sein, was Sie versuchen.

Was Sie also wollen, ist:

Article.objects.in_bulk([1, 2, 3])

6

Falls wir programmgesteuert festlegen möchten, welches Datenbankfeld wir abfragen möchten:

import operator
questions = [('question__contains', 'test'), ('question__gt', 23 )]
q_list = [Q(x) for x in questions]
Poll.objects.filter(reduce(operator.or_, q_list))

6

Lösung, die reduceund or_Operatoren zum Filtern nach Multiplikationsfeldern verwendet.

from functools import reduce
from operator import or_
from django.db.models import Q

filters = {'field1': [1, 2], 'field2': ['value', 'other_value']}

qs = Article.objects.filter(
   reduce(or_, (Q(**{f'{k}__in': v}) for k, v in filters.items()))
)

ps fist ein neues Format für Zeichenfolgenliteral. Es wurde in Python 3.6 eingeführt


4

Mit dem Operator | = können Sie eine Abfrage mithilfe von Q-Objekten programmgesteuert aktualisieren.


2
Ist das irgendwo dokumentiert? Ich habe in den letzten 15 Minuten gesucht und dies ist das einzige, was ich finden kann.
Wobbily_col

Wie so viel in unserer Branche ist es auf StackOverflow dokumentiert!
Chris

2

Dieser ist für dynamische pk Liste:

pk_list = qs.values_list('pk', flat=True)  # i.e [] or [1, 2, 3]

if len(pk_list) == 0:
    Article.objects.none()

else:
    q = None
    for pk in pk_list:
        if q is None:
            q = Q(pk=pk)
        else:
            q = q | Q(pk=pk)

    Article.objects.filter(q)

Sie können q = Q()stattdessen q = Nonedie if q is NoneKlausel verwenden und dann entfernen - etwas weniger effizient, aber drei Codezeilen entfernen. (Das leere Q wird anschließend zusammengeführt, wenn die Abfrage ausgeführt wird.)
Chris

1

Eine andere Möglichkeit war ich nicht bewusst , bis vor kurzem - QuerySetauch außer Kraft setzt &, |, ~usw, Betreiber. Die anderen Antworten, dass OR Q-Objekte eine bessere Lösung für diese Frage sind, aber aus Gründen des Interesses / der Argumentation können Sie Folgendes tun:

id_list = [1, 2, 3]
q = Article.objects.filter(pk=id_list[0])
for i in id_list[1:]:
    q |= Article.objects.filter(pk=i)

str(q.query)gibt eine Abfrage mit allen Filtern in der WHEREKlausel zurück.


1

For-Schleife:

values = [1, 2, 3]
q = Q(pk__in=[]) # generic "always false" value
for val in values:
    q |= Q(pk=val)
Article.objects.filter(q)

Reduzieren:

from functools import reduce
from operator import or_

values = [1, 2, 3]
q_objects = [Q(pk=val) for val in values]
q = reduce(or_, q_objects, Q(pk__in=[]))
Article.objects.filter(q)

Beide sind gleichbedeutend mit Article.objects.filter(pk__in=values)

Es ist wichtig zu überlegen, was Sie wollen, wenn valueses leer ist. Viele Antworten mit Q()als Startwert geben alles zurück . Q(pk__in=[])ist ein besserer Startwert. Es ist ein immer fehlerhaftes Q-Objekt, das vom Optimierer gut verarbeitet wird (auch bei komplexen Gleichungen).

Article.objects.filter(Q(pk__in=[]))  # doesn't hit DB
Article.objects.filter(Q(pk=None))    # hits DB and returns nothing
Article.objects.none()                # doesn't hit DB
Article.objects.filter(Q())           # returns everything

Wenn Sie alles zurückgeben möchten , wenn valueses leer ist, sollten Sie UND mit ~Q(pk__in=[]), um dieses Verhalten sicherzustellen:

values = []
q = Q()
for val in values:
    q |= Q(pk=val)
Article.objects.filter(q)                     # everything
Article.objects.filter(q | author="Tolkien")  # only Tolkien

q &= ~Q(pk__in=[])
Article.objects.filter(q)                     # everything
Article.objects.filter(q | author="Tolkien")  # everything

Es ist wichtig , sich daran zu erinnern , dass Q()ist nichts , kein Immer nachfolgenden Q - Objekt. Bei jeder Operation wird es einfach vollständig gelöscht.


0

einfach ..
aus django.db.models importieren Q importieren Sie Modell args = (Q (Sichtbarkeit = 1) | (Q (Sichtbarkeit = 0) & Q (Benutzer = self.user))) #Tuple parameters = {} #dic order = 'create_at' limit = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.