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


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)

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

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)  

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

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)

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

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

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


4

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


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)

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.


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.


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]
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.