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