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

Jack Ha
quelle
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)
Dave Webb
quelle
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
außen
quelle
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)  
Tom Viner
quelle
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))
Ignacio Vazquez-Abrams
quelle
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)
alex vasi
quelle
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])
Dominic Rodger
quelle
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))
zzart
quelle
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

Ivan Semochkin
quelle
4

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

Jeff Ober
quelle
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)
Velodee
quelle
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.

Chris
quelle
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.

Jonathan Richards
quelle
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]
Alfonsoolavarria
quelle