Como compor dinamicamente um filtro de consulta OR no Django?


104

A partir de um exemplo, você pode ver um filtro de consulta OR múltipla:

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

Por exemplo, isso resulta em:

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

No entanto, desejo criar esse filtro de consulta a partir de uma lista. Como fazer isso?

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


1
Parece que você perguntou isso duas vezes: stackoverflow.com/questions/852404
— Dominic Rodger

Para este caso de uso específico, você provavelmente usaria Article.objects.filter(pk__in=[1, 2, 3])no django moderno, mas a questão ainda é relevante se você quiser fazer algo um pouco mais avançado fazendo OR'ing de objetos Q juntos.
— beruic

Respostas:


162

Você pode encadear suas consultas da seguinte maneira:

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
Obrigado! Era isso que eu procurava :) Não sabia que você poderia fazer | =
— Jack Ha

23
Você também pode inicializar a consulta usando: query = Q ()
— chachan

5
você pode fazer campos dinâmicos usando ** {'fieldname': value}: queries = [Q (** {'fieldname': value}) para valor em valores]
— rechie

1
Como você pode compor consultas brutas com Django se quiser adicionar condições opcionais como acima?
— usuário

Isso não funcionou para mim, não sei por quê. as consultas retornam zero resultados para mim
— Mehran Nouri

83

Para construir consultas mais complexas, há também a opção de usar as constantes Q.OR e Q.AND do objeto Q () embutidas junto com o método add () como:

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 iniciantes neste tópico, como eu, acho que esta resposta deve ser considerada a resposta principal. É mais Django do que a resposta aceita. Obrigado!
— theresaanna

5
Eu debateria se é mais pítônico usar os operadores OR e AND integrados (| e &). q_objects |= Q(pk=item)
— Bobort

Perfeito! Obrigado!
— RL Shyam

1
Vale a pena observar que se listestiver vazio, você retornará o equivalente a Article.objects.all(). Fácil de mitigar voltando Article.objects.none()para aquele teste.
— Wil

2
@Wil você também pode inicializar q_objectscom Q(id__in=[]). Ele sempre falhará, a menos que seja combinado com algo e o otimizador de consulta lidará bem com isso.
— Jonathan Richards

44

Uma maneira mais rápida de escrever a resposta de Dave Webb usando a função de redução do 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 a redução "embutida" foi removida e substituída por functools.reduce. fonte
— lsowen

Obrigado @lsowen, corrigido.
— Tom Viner

E é possível usar em operator.or_vez do 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, mas de onde operatorveio?
— mpiskore

1
@mpiskore: No mesmo lugar que qualquer outro módulo Python: você o importa.
— Ignacio Vazquez-Abrams

1
engraçado. essa era realmente a minha dúvida: em qual módulo / biblioteca posso encontrá-lo? o google não ajudou muito.
— mpiskore

oh, pensei que fosse algum tipo de operador ORM de Django. Que bobo da minha parte, obrigado!
— mpiskore

20

Talvez seja melhor usar a instrução sql IN.

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

Consulte a referência da API do queryset .

Se você realmente precisa fazer consultas com lógica dinâmica, pode fazer algo assim (feio + não testado):

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

1
Você também pode usarquery |= Q(field=cond)
— Bobort

8

Veja a documentação :

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

Observe que esse método só funciona para pesquisas de chave primária, mas parece ser isso que você está tentando fazer.

Então o que você quer é:

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

6

Caso desejemos definir programaticamente qual campo db 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

Solução que utiliza reducee or_operadores para filtrar por campos de multiplicação.

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é um novo literal de strings de formato. Foi introduzido no python 3.6


4

Você pode usar o operador | = para atualizar programaticamente uma consulta usando objetos Q.


2
Isso está documentado em algum lugar? Estou procurando há 15 minutos e esta é a única coisa que posso encontrar.
— wobbily_col

Como muito em nosso setor, está documentado no StackOverflow!
— Chris de

2

Este é para a lista pk dinâmica:

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)

Você pode usar em q = Q()vez de q = Nonee remover a if q is Nonecláusula - um pouco menos eficiente, mas pode remover três linhas de código. (O Q vazio é posteriormente mesclado quando a consulta é executada.)
— Chris

1

Outra opção que eu não estava ciente de até recentemente - QuerySettambém substitui os &, |, ~, etc, operadores. As outras respostas que os objetos OR Q são uma solução melhor para esta questão, mas por uma questão de interesse / argumento, você pode fazer:

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)retornará uma consulta com todos os filtros da WHEREcláusula.


1

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

Reduzir:

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 são equivalentes a Article.objects.filter(pk__in=values)

É importante considerar o que você deseja quando valuesestá vazio. Muitas respostas com Q()como valor inicial retornarão tudo . Q(pk__in=[])é um valor inicial melhor. É um objeto Q que sempre falha que é bem tratado pelo otimizador (mesmo para equações complexas).

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 quiser retornar tudo quando valuesestiver vazio, você deve usar E com ~Q(pk__in=[])para garantir esse 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 lembrar que nãoQ() é nada , nem um objeto Q sempre bem-sucedido. Qualquer operação que o envolva simplesmente o abandonará completamente.


0

fácil ..
de django.db.models import Q import você modelar args = (Q (visibilidade = 1) | (Q (visibilidade = 0) & Q (usuário = self.user))) #Tuple parameters = {} #dic order = limite de 'criar_at' = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.