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.