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.