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


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)

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

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)  

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

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)

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

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

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


4

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


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)

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.


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.


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]
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.