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

Jack Ha
źródło
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)
Dave Webb
źródło
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
exside
źródło
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)  
Tom Viner
źródło
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))
Ignacio Vazquez-Abrams
źródło
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)
alex vasi
źródło
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])
Dominic Rodger
źródło
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))
zzart
źródło
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

Ivan Semochkin
źródło
4

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

Jeff Ober
źródło
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)
Velodee
źródło
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.

Chris
źródło
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.

Jonathan Richards
źródło
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]
alfonsoolavarria
źródło