Come comporre dinamicamente un filtro di query OR in Django?


104

Da un esempio puoi vedere un filtro di query OR multiplo:

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

Ad esempio, questo si traduce in:

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

Tuttavia, desidero creare questo filtro di query da un elenco. Come farlo?

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


1
Sembra che tu l'abbia chiesto due volte: stackoverflow.com/questions/852404
Dominic Rodger,

Per questo caso d'uso specifico probabilmente Article.objects.filter(pk__in=[1, 2, 3])useresti nel django moderno, ma la domanda è ancora pertinente se vuoi fare qualcosa di un po 'più avanzato facendo OR insieme agli oggetti Q.
beruic

Risposte:


162

Puoi concatenare le tue query come segue:

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
Grazie! Questo era quello che stavo cercando :) Non sapevo che potessi fare | =
Jack Ha

23
È anche possibile inizializzare la query utilizzando: query = Q ()
chachan

5
puoi creare campi dinamici utilizzando ** {'fieldname': value}: queries = [Q (** {'fieldname': value}) for value in values]
rechie

1
Come puoi comporre query non elaborate con Django se vuoi aggiungere condizioni opzionali come sopra?
utente

Non ha funzionato per me, non so perché. le query restituiscono zero risultati per me
Mehran Nouri

83

Per creare query più complesse c'è anche la possibilità di utilizzare le costanti Q.OR e Q.AND degli oggetti Q () incorporate insieme al metodo add () in questo modo:

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
Per i nuovi arrivati ​​in questo thread, come me, penso che questa risposta dovrebbe essere considerata la risposta migliore. È più djangoesque della risposta accettata. Grazie!
theresaanna

5
Vorrei discutere sul fatto che è più pitonico utilizzare gli operatori OR e AND incorporati (| e &). q_objects |= Q(pk=item)
Bobort

Perfetto! Grazie!
RL Shyam

1
Vale la pena notare che se listrisulta vuoto restituirai l'equivalente di Article.objects.all(). Facile da mitigare tornando Article.objects.none()per quel test però.
Wil

2
@Wil puoi anche inizializzare q_objectscon Q(id__in=[]). Fallirà sempre a meno che non venga eseguito un OR con qualcosa e l'ottimizzatore di query lo gestirà bene.
Jonathan Richards,

44

Un modo più breve per scrivere la risposta di Dave Webb usando la funzione reduce di python :

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

Sembra che la riduzione "incorporata" sia stata rimossa e sostituita con functools.reduce. fonte
lsowen

Grazie @lsowen, risolto.
Tom Viner

Ed è possibile utilizzare al operator.or_posto del lambda.
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, ma da dove viene operator?
mpiskore

1
@mpiskore: stesso posto di ogni altro modulo Python: lo importi tu.
Ignacio Vazquez-Abrams

1
divertente. questa era davvero la mia domanda: in quale modulo / libreria posso trovarlo? Google non ha aiutato molto.
mpiskore

oh, pensavo fosse una sorta di operatore ORM Django. Che sciocco da parte mia, grazie!
mpiskore

20

Forse è meglio usare l'istruzione sql IN.

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

Vedi riferimento api queryset .

Se hai davvero bisogno di fare query con logica dinamica, puoi fare qualcosa del genere (brutto + non testato):

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

1
Puoi anche usarequery |= Q(field=cond)
Bobort

8

Vedi i documenti :

>>> 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([])
{}

Nota che questo metodo funziona solo per le ricerche di chiavi primarie, ma sembra essere quello che stai cercando di fare.

Quindi quello che vuoi è:

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

6

Nel caso in cui desideriamo impostare a livello di codice quale campo db vogliamo interrogare:

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

Soluzione che utilizzare reducee or_operatori per filtrare moltiplicando i campi.

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 fè un nuovo formato di stringhe letterale. È stato introdotto in python 3.6


4

È possibile utilizzare l'operatore | = per aggiornare a livello di codice una query utilizzando oggetti Q.


2
È documentato da qualche parte? Ho cercato negli ultimi 15 minuti e questa è l'unica cosa che riesco a trovare.
wobbily_col

Come così tanto nel nostro settore, è documentato su StackOverflow!
Chris

2

Questo è per l'elenco pk dinamico:

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)

Potresti usare al q = Q()posto di q = None, quindi rimuovere la if q is Noneclausola - leggermente meno efficiente ma puoi rimuovere tre righe di codice. (La Q vuota viene successivamente fusa quando viene eseguita la query.)
Chris

1

Un'altra opzione che non era a conoscenza di fino a poco tempo - QuerySetprevale anche i &, |, ~, ecc, operatori. Le altre risposte agli oggetti OR Q sono una soluzione migliore a questa domanda, ma per motivi di interesse / argomento, puoi fare:

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)restituirà una query con tutti i filtri nella WHEREclausola.


1

Per loop:

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

Ridurre:

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)

Entrambi sono equivalenti a Article.objects.filter(pk__in=values)

È importante considerare quello che vuoi quando valuesè vuoto. Molte risposte con Q()come valore iniziale restituiranno tutto . Q(pk__in=[])è un valore iniziale migliore. È un oggetto Q sempre fallimentare che viene gestito bene dall'ottimizzatore (anche per equazioni complesse).

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

Se vuoi restituire tutto quando valuesè vuoto, dovresti AND con ~Q(pk__in=[])per assicurarti che il comportamento:

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

È importante ricordare che nonQ() è niente , non è un oggetto Q sempre riuscito. Qualsiasi operazione che lo coinvolge lo farà cadere completamente.


0

facile ..
da django.db.models importa Q importa il tuo modello args = (Q (visibilità = 1) | (Q (visibilità = 0) & Q (user = self.user))) #Tuple parameters = {} #dic order = limite 'create_at' = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.