Jak dynamicznie utworzyć filtr zapytania OR w Django?


104

Na przykładzie możesz zobaczyć filtr wielu zapytań OR:

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

Na przykład skutkuje to:

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

Jednak chcę utworzyć ten filtr kwerendy z listy. Jak to zrobić?

na przykład [1, 2, 3] -> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))


1
Wygląda na to, że zadałeś to dwa razy: stackoverflow.com/questions/852404
— Dominic Rodger

W tym konkretnym przypadku użycia prawdopodobnie Article.objects.filter(pk__in=[1, 2, 3])użyłbyś w nowoczesnym django, ale pytanie jest nadal aktualne, jeśli chcesz zrobić coś bardziej zaawansowanego, wykonując razem OR'owanie obiektów Q.
— beruic

Odpowiedzi:


162

Możesz połączyć swoje zapytania w następujący sposób:

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
Dzięki! Tego właśnie szukałem :) Nie wiedziałem, że możesz to zrobić | =
— Jack Ha

23
Możesz również zainicjować zapytanie za pomocą: query = Q ()
— chachan

5
możesz tworzyć pola dynamiczne, używając ** {'fieldname': value}: queries = [Q (** {'fieldname': value}) for value in values]
— rechie

1
Jak możesz komponować nieprzetworzone zapytania za pomocą Django, jeśli chcesz dodać opcjonalne warunki, takie jak powyżej?
— użytkownik

To mi się nie udało, nie wiem dlaczego. zapytania zwracają dla mnie zero wyników
— Mehran Nouri

83

Aby zbudować bardziej złożone zapytania, istnieje również opcja użycia wbudowanych w Q () stałych obiektu Q.OR i Q.AND wraz z metodą add () w następujący sposób:

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
Dla nowicjuszy w tym wątku, takich jak ja, uważam, że ta odpowiedź jest najlepsza. Jest bardziej djangoeski niż przyjęta odpowiedź. Dziękuję Ci!
— theresaanna

5
Chciałbym debatować, że bardziej pythonowe jest użycie wbudowanych operatorów OR i AND (| i &). q_objects |= Q(pk=item)
— Bobort

Idealny! Dziękuję Ci!
— RL Shyam

1
Warto zauważyć, że jeśli okaże listsię, że jest pusty, zwrócisz równowartość Article.objects.all(). Łatwo jednak złagodzić, wracając Article.objects.none()do tego testu.
— Wil

2
@Wil możesz również zainicjować za q_objectspomocą Q(id__in=[]). Zawsze kończy się niepowodzeniem, chyba że zostanie z czymś poddany OR, a optymalizator zapytań ładnie to obsłuży.
— Jonathan Richards

44

Krótszy sposób napisania odpowiedzi Dave'a Webba za pomocą funkcji redukuj w Pythonie :

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

Wygląda na to, że „wbudowana” redukcja została usunięta i zastąpiona przez functools.reduce. źródło
— lsowen

Dzięki @lsowen, naprawione.
— Tom Viner,

I można użyć operator.or_zamiast lambdy.
— 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, ale skąd się operatorwzięło?
— mpiskore

1
@mpiskore: To samo miejsce co każdy inny moduł Pythona: importujesz go.
— Ignacio Vazquez-Abrams

1
zabawny. to było naprawdę moje pytanie: w którym module / bibliotece mogę to znaleźć? Google niewiele pomogło.
— mpiskore

och, myślałem, że to jakiś operator Django ORM. Jak głupio z mojej strony, dzięki!
— mpiskore

20

Może lepiej jest użyć instrukcji sql IN.

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

Zobacz informacje o API queryset .

Jeśli naprawdę potrzebujesz tworzyć zapytania z dynamiczną logiką, możesz zrobić coś takiego (brzydkie + nie przetestowane):

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

1
Możesz również użyćquery |= Q(field=cond)
— Bobort

8

Zobacz dokumentację :

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

Zwróć uwagę, że ta metoda działa tylko w przypadku wyszukiwania klucza podstawowego, ale wydaje się, że to jest to, co próbujesz zrobić.

Więc chcesz:

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

6

W przypadku, gdy chcemy programowo ustawić, o które pole db chcemy zapytać:

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

Rozwiązanie wykorzystujące reducei or_operatory do filtrowania przez mnożenie pól.

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 fto nowy literał łańcuchów formatu. Został wprowadzony w Pythonie 3.6


4

Za pomocą operatora | = można programowo aktualizować zapytanie przy użyciu obiektów Q.


2
Czy jest to gdzieś udokumentowane? Szukałem przez ostatnie 15 minut i to jedyna rzecz, jaką mogę znaleźć.
— wobbily_col

Podobnie jak w naszej branży, jest to udokumentowane w witrynie StackOverflow!
— Chris

2

Ten jest dla dynamicznej listy 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)

Możesz użyć q = Q()zamiast q = None, a następnie usunąć if q is Noneklauzulę - nieco mniej wydajne, ale możesz usunąć trzy wiersze kodu. (Puste Q jest następnie łączone, gdy zapytanie jest uruchamiane).
— Chris

1

Inna opcja nie była świadoma do niedawna - QuerySetrównież nadpisuje &, |, ~, etc, operatorów. Inne odpowiedzi, że obiekty OR Q są lepszym rozwiązaniem na to pytanie, ale ze względu na zainteresowanie / argumentację możesz zrobić:

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)zwróci jedno zapytanie ze wszystkimi filtrami w WHEREklauzuli.


1

Dla pętli:

values = [1, 2, 3]
q = Q(pk__in=[]) # generic "always false" value
for val in values:
    q |= Q(pk=val)
Article.objects.filter(q)

Redukować:

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)

Oba są równoważne Article.objects.filter(pk__in=values)

Ważne jest, aby rozważyć, czego chcesz, gdy valuesjest pusty. Wiele odpowiedzi z Q()wartością początkową zwróci wszystko . Q(pk__in=[])to lepsza wartość początkowa. Jest to zawsze zawodzący obiekt Q, który jest dobrze obsługiwany przez optymalizator (nawet w przypadku złożonych równań).

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

Jeśli chcesz zwrócić wszystko, gdy valuesjest puste, powinieneś ORAZ z, ~Q(pk__in=[])aby zapewnić to zachowanie:

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

Należy pamiętać, że Q()to nic , nie zawsze kolejny obiekt Q. Każda operacja z tym związana po prostu całkowicie go upuści.


0

easy ..
from django.db.models import Q import you model args = (Q (visibility = 1) | (Q (visibility = 0) & Q (user = self.user))) #Tuple parameters = {} #dic order = limit „create_at” = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.