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

Jack Ha
fonte
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)
Dave Webb
fonte
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
lado externo
fonte
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)  
Tom Viner
fonte
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))
Ignacio Vazquez-Abrams
fonte
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)
alex vasi
fonte
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])
Dominic Rodger
fonte
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))
zzart
fonte
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

Ivan Semochkin
fonte
4

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

Jeff Ober
fonte
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)
Velodee
fonte
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.

Chris
fonte
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.

Jonathan Richards
fonte
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]
alfonsoolavarria
fonte