¿Cómo componer dinámicamente un filtro de consulta OR en Django?

104

En un ejemplo, puede ver un filtro de consulta OR múltiple:

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

Por ejemplo, esto da como resultado:

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

Sin embargo, quiero crear este filtro de consulta a partir de una lista. ¿Como hacer eso?

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

Jack Ha
fuente
1
Parece haber preguntado esto dos veces: stackoverflow.com/questions/852404
Dominic Rodger
Para este caso de uso específico, probablemente lo usaría Article.objects.filter(pk__in=[1, 2, 3])en django moderno, pero la pregunta sigue siendo relevante si desea hacer algo un poco más avanzado al combinar objetos Q con OR.
beruic

Respuestas:

162

Puede encadenar sus consultas de la siguiente manera:

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
fuente
3
¡Gracias! Esto era lo que estaba buscando :) No sabía que podía hacer | =
Jack Ha
23
También puede inicializar la consulta usando: query = Q ()
chachan
5
puede crear campos dinámicos usando ** {'fieldname': value}: queries = [Q (** {'fieldname': value}) para valor en valores]
rechie
1
¿Cómo puede redactar consultas sin procesar con Django si desea agregar condiciones opcionales como las anteriores?
usuario
Eso no funcionó para mí, no sé por qué. las consultas devuelven cero resultados para mí
Mehran Nouri
83

Para crear consultas más complejas, también existe la opción de usar las constantes Q.OR y Q.AND del objeto Q () integradas junto con el método add () así:

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
exside
fuente
12
Para los recién llegados a este hilo, como yo, creo que esta respuesta debería considerarse como la respuesta principal. Es más djangoesque que la respuesta aceptada. ¡Gracias!
theresaanna
5
Yo debatiría que es más pitónico usar los operadores integrados OR y AND (| y &). q_objects |= Q(pk=item)
Bobort
¡Perfecto! ¡Gracias!
RL Shyam
1
Vale la pena señalar que si listestá vacío, devolverá el equivalente de Article.objects.all(). Sin Article.objects.none()embargo, es fácil de mitigar regresando para esa prueba.
Wil
2
@Wil también puede inicializar q_objectscon Q(id__in=[]). Siempre fallará a menos que se haga OR con algo y el optimizador de consultas lo manejará bien.
Jonathan Richards
44

Una forma más corta de escribir la respuesta de Dave Webb usando la función de reducción de 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)  
Tom Viner
fuente
Parece que la reducción "incorporada" se eliminó y se reemplazó por functools.reduce. fuente
lsowen
Gracias @lsowen, arreglado.
Tom Viner
Y es posible usar en operator.or_lugar de 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))
Ignacio Vázquez-Abrams
fuente
Ok, pero ¿de dónde operatorviene el?
mpiskore
1
@mpiskore: el mismo lugar que cualquier otro módulo de Python: usted lo importa.
Ignacio Vazquez-Abrams
1
gracioso. esa era realmente mi pregunta: ¿en qué módulo / biblioteca puedo encontrarlo? google no ayudó mucho.
mpiskore
oh, pensé que era una especie de operador ORM de Django. ¡Qué tonto soy, gracias!
mpiskore
20

Tal vez sea mejor usar la instrucción sql IN.

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

Ver referencia de la API de queryset .

Si realmente necesita hacer consultas con lógica dinámica, puede hacer algo como esto (feo + no probado):

query = Q(field=1)
for cond in (2, 3):
    query = query | Q(field=cond)
Article.objects.filter(query)
alex vasi
fuente
1
También puede utilizarquery |= Q(field=cond)
Bobort
8

Ver los documentos :

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

Tenga en cuenta que este método solo funciona para búsquedas de claves primarias, pero eso parece ser lo que está tratando de hacer.

Entonces lo que quieres es:

Article.objects.in_bulk([1, 2, 3])
Dominic Rodger
fuente
6

En caso de que queramos establecer programáticamente qué campo de base de datos queremos consultar:

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
fuente
6

Solución que utilizan reducey or_operadores para filtrar por multiplicar campos.

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 fes un nuevo formato literal de cadenas. Fue introducido en Python 3.6

Ivan Semochkin
fuente
4

Puede utilizar el operador | = para actualizar mediante programación una consulta utilizando objetos Q.

Jeff Ober
fuente
2
¿Está esto documentado en alguna parte? He estado buscando durante los últimos 15 minutos y esto es lo único que puedo encontrar.
wobbily_col
Como muchas otras cosas en nuestra industria, ¡está documentado en StackOverflow!
Chris
2

Este es para la lista dinámica de pk:

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
fuente
Puede usar en q = Q()lugar de q = None, luego eliminar la if q is Nonecláusula, un poco menos eficiente pero puede eliminar tres líneas de código. (La Q vacía se fusiona posteriormente cuando se ejecuta la consulta)
Chris
1

Otra opción que no estaba al tanto de que hasta hace poco - QuerySettambién anula los &, |, ~, etc, operadores. El otro responde que los objetos OR Q son una mejor solución a esta pregunta, pero por el interés / argumento, puede hacer:

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)devolverá una consulta con todos los filtros de la WHEREcláusula.

Chris
fuente
1

En bucle:

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

Reducir:

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)

Ambos son equivalentes a Article.objects.filter(pk__in=values)

Es importante considerar lo que quiere cuando valuesestá vacío. Muchas respuestas con Q()un valor inicial devolverán todo . Q(pk__in=[])es un mejor valor inicial. Es un objeto Q que falla siempre y que el optimizador maneja bien (incluso para ecuaciones complejas).

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

Si desea devolver todo cuando valuesestá vacío, debe Y con ~Q(pk__in=[])para garantizar ese comportamiento:

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 importante recordar que noQ() es nada , no un objeto Q siempre exitoso. Cualquier operación que lo involucre simplemente lo eliminará por completo.

Jonathan Richards
fuente
0

fácil ...
desde django.db.models import Q importa tu modelo args = (Q (visibilidad = 1) | (Q (visibilidad = 0) & Q (usuario = self.user))) #Tuple parámetros = {} # orden dic = 'crear_en' límite = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
alfonsoolavarria
fuente