Bagaimana secara dinamis membuat penyaring permintaan OR di Django?

104

Dari sebuah contoh, Anda dapat melihat beberapa filter ATAU kueri:

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

Misalnya, ini menghasilkan:

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

Namun, saya ingin membuat filter kueri ini dari daftar. Bagaimana cara melakukannya?

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

Jack Ha
sumber
1
Anda tampaknya telah menanyakan ini dua kali: stackoverflow.com/questions/852404
Dominic Rodger
Untuk kasus penggunaan khusus ini Anda mungkin akan menggunakan Article.objects.filter(pk__in=[1, 2, 3])dalam django modern, tetapi pertanyaannya masih relevan jika Anda ingin melakukan sesuatu yang lebih mahir dengan OR'ing objek Q bersama-sama.
beruic

Jawaban:

162

Anda dapat merangkai kueri Anda sebagai berikut:

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
sumber
3
Terima kasih! Inilah yang saya cari :) Tidak tahu Anda bisa melakukan | =
Jack Ha
23
Anda juga bisa menginisialisasi kueri menggunakan: query = Q ()
chachan
5
Anda dapat membuat bidang dinamis dengan menggunakan ** {'fieldname': value}: queries = [Q (** {'fieldname': value}) untuk nilai dalam nilai]
ulangi
1
Bagaimana anda dapat membuat query mentah dengan Django jika anda ingin menambahkan kondisi opsional seperti di atas?
pengguna
Itu tidak berhasil untuk saya, saya tidak tahu mengapa. kueri mengembalikan hasil nol untuk saya
Mehran Nouri
83

Untuk membangun kueri yang lebih kompleks, ada juga opsi untuk menggunakan konstanta objek Q () bawaan Q.OR dan Q.AND bersama dengan metode add () seperti:

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
sumber
12
Untuk pendatang baru di utas ini, seperti saya, saya pikir jawaban ini harus dianggap sebagai jawaban teratas. Ini lebih Djangoesque daripada jawaban yang diterima. Terima kasih!
theresaanna
5
Saya akan memperdebatkan bahwa lebih pythonic menggunakan operator builtin OR dan AND (| dan &). q_objects |= Q(pk=item)
Bobort
Sempurna! Terima kasih!
RL Shyam
1
Perlu dicatat bahwa jika listkebetulan kosong Anda akan mengembalikan yang setara Article.objects.all(). Mudah untuk dikurangi dengan kembali Article.objects.none()untuk tes itu.
Wil
2
@Wil Anda juga dapat menginisialisasi q_objectsdengan Q(id__in=[]). Itu akan selalu gagal kecuali ORed dengan sesuatu dan pengoptimal kueri akan menanganinya dengan baik.
Jonathan Richards
44

Cara yang lebih singkat untuk menulis jawaban Dave Webb menggunakan fungsi pengurangan 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
sumber
Sepertinya pengurangan "bawaan" telah dilepas dan diganti dengan functools.reduce. sumber
lsowen
Terima kasih @lsowen, sudah diperbaiki.
Tom Viner
Dan itu mungkin untuk digunakan operator.or_sebagai pengganti 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
sumber
Oke, tapi dari mana operatorasalnya?
mpiskore
1
@mpiskore: Tempat yang sama seperti setiap modul Python lainnya: Anda mengimpornya.
Ignacio Vazquez-Abrams
1
lucu. itu benar-benar pertanyaan saya: di modul / pustaka mana saya dapat menemukannya? google tidak banyak membantu.
mpiskore
oh, saya pikir itu semacam operator Django ORM. Betapa konyolnya aku, terima kasih!
mpiskore
20

Mungkin lebih baik menggunakan pernyataan sql IN.

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

Lihat referensi api queryset .

Jika Anda benar-benar perlu membuat kueri dengan logika dinamis, Anda dapat melakukan sesuatu seperti ini (jelek + tidak diuji):

query = Q(field=1)
for cond in (2, 3):
    query = query | Q(field=cond)
Article.objects.filter(query)
alex vasi
sumber
1
Anda juga bisa menggunakanquery |= Q(field=cond)
Bobort
8

Lihat dokumennya :

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

Perhatikan bahwa metode ini hanya berfungsi untuk pencarian kunci utama, tetapi tampaknya itulah yang Anda coba lakukan.

Jadi yang Anda inginkan adalah:

Article.objects.in_bulk([1, 2, 3])
Dominic Rodger
sumber
6

Jika kita ingin mengatur secara terprogram bidang db apa yang ingin kita kueri:

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
sumber
6

Solusi yang digunakan reducedan or_operator untuk memfilter dengan mengalikan bidang.

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 fadalah format string literal baru. Itu diperkenalkan di python 3.6

Ivan Semochkin
sumber
4

Anda dapat menggunakan operator | = untuk secara terprogram memperbarui kueri menggunakan objek Q.

Jeff Ober
sumber
2
Apakah ini didokumentasikan di mana saja? Saya telah mencari selama 15 menit terakhir, dan ini adalah satu-satunya hal yang dapat saya temukan.
wobbily_col
Seperti banyak hal di industri kami, ini didokumentasikan di StackOverflow!
Chris
2

Yang ini untuk daftar pk dinamis:

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
sumber
Anda bisa menggunakan q = Q()bukan q = None, kemudian menghapus if q is Noneklausul - sedikit kurang efisien namun dapat menghapus tiga baris kode. (Q kosong kemudian digabungkan saat kueri dijalankan.)
Chris
1

Pilihan lain Saya tidak menyadari sampai saat ini - QuerySetjuga menimpa &, |, ~, dll, operator. Jawaban lain bahwa objek OR Q adalah solusi yang lebih baik untuk pertanyaan ini, tetapi demi kepentingan / argumen, Anda dapat melakukan:

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)akan mengembalikan satu kueri dengan semua filter di WHEREklausa.

Chris
sumber
1

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

Mengurangi:

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)

Keduanya setara dengan Article.objects.filter(pk__in=values)

Penting untuk mempertimbangkan apa yang Anda inginkan saat valueskosong. Banyak jawaban dengan Q()nilai awal akan mengembalikan semuanya . Q(pk__in=[])adalah nilai awal yang lebih baik. Ini adalah objek Q yang selalu gagal yang ditangani dengan baik oleh pengoptimal (bahkan untuk persamaan kompleks).

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

Jika Anda ingin mengembalikan semuanya saat valueskosong, Anda harus DAN dengan ~Q(pk__in=[])untuk memastikan perilaku itu:

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

Penting untuk diingat bahwa Q()bukan apa - apa , bukan objek Q yang selalu berhasil. Operasi apa pun yang melibatkannya hanya akan menjatuhkannya sepenuhnya.

Jonathan Richards
sumber
0

mudah ..
dari django.db.models import Q import you model args = (Q (visibility = 1) | (Q (visibility = 0) & Q (user = self.user))) #Tuple parameter = {} #dic order = 'create_at' limit = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
alfonsoolavarria.dll
sumber