ชุดกรอง Django __in สำหรับ * ทุก * รายการในรายการ


104

สมมติว่าฉันมีโมเดลต่อไปนี้

class Photo(models.Model):
    tags = models.ManyToManyField(Tag)

class Tag(models.Model):
    name = models.CharField(max_length=50)

ในมุมมองของฉันมีรายการที่มีฟิลเตอร์ที่ใช้งานเรียกว่าประเภท ฉันต้องการที่จะกรองวัตถุรูปภาพที่มีแท็กทั้งหมดนำเสนอในหมวดหมู่

ฉันเหนื่อย:

Photo.objects.filter(tags__name__in=categories)

แต่จะตรงกับรายการใด ๆในหมวดหมู่ไม่ใช่ทุกรายการ

ดังนั้นหากหมวดหมู่เป็น ['วันหยุด', 'ฤดูร้อน'] ฉันต้องการให้รูปภาพมีทั้งแท็กวันหยุดและฤดูร้อน

สามารถทำได้หรือไม่?


7
อาจจะ: qs = Photo.objects.all (); สำหรับหมวดหมู่ในหมวดหมู่: qs = qs.filter (tags__name = category)
jpic

2
jpic ถูกต้องPhoto.objects.filter(tags__name='holiday').filter(tags__name='summer')เป็นวิธีที่จะไป (นี่เหมือนกับตัวอย่างของ jpic) แต่ละคนfilterควรเพิ่มคำอธิบายเพิ่มเติมJOINในแบบสอบถามเพื่อให้คุณสามารถใช้วิธีการบันทึกย่อได้หากมีจำนวนมากเกินไป
Davor Lucic

1
นี่คือข้อมูลอ้างอิงในเอกสาร: docs.djangoproject.com/en/dev/topics/db/queries/…
sgallen

คุณคาดหวังว่าจะมีฟังก์ชันในตัวสำหรับสิ่งนี้โดย Django
Vincent

คำตอบ:


125

สรุป:

ทางเลือกหนึ่งคือตามที่แนะนำโดย jpic และ sgallen ในความคิดเห็นเพื่อเพิ่ม.filter()สำหรับแต่ละหมวดหมู่ แต่ละส่วนเพิ่มเติมfilterจะเพิ่มการรวมเพิ่มเติมซึ่งไม่ควรเป็นปัญหาสำหรับหมวดหมู่ชุดเล็ก

มีคือการรวม วิธีการ ข้อความค้นหานี้จะสั้นลงและอาจเร็วกว่าสำหรับหมวดหมู่จำนวนมาก

นอกจากนี้คุณยังมีตัวเลือกของการใช้คำสั่งที่กำหนดเอง


ตัวอย่างบางส่วน

การตั้งค่าการทดสอบ:

class Photo(models.Model):
    tags = models.ManyToManyField('Tag')

class Tag(models.Model):
    name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.name

In [2]: t1 = Tag.objects.create(name='holiday')
In [3]: t2 = Tag.objects.create(name='summer')
In [4]: p = Photo.objects.create()
In [5]: p.tags.add(t1)
In [6]: p.tags.add(t2)
In [7]: p.tags.all()
Out[7]: [<Tag: holiday>, <Tag: summer>]

การใช้ตัวกรองที่ถูกล่ามโซ่ :

In [8]: Photo.objects.filter(tags=t1).filter(tags=t2)
Out[8]: [<Photo: Photo object>]

แบบสอบถามผลลัพธ์:

In [17]: print Photo.objects.filter(tags=t1).filter(tags=t2).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_photo_tags" T4 ON ("test_photo"."id" = T4."photo_id")
WHERE ("test_photo_tags"."tag_id" = 3  AND T4."tag_id" = 4 )

โปรดทราบว่าแต่ละรายการfilterจะเพิ่มมากขึ้นJOINSในแบบสอบถาม

ใช้วิธีการใส่คำอธิบายประกอบ :

In [29]: from django.db.models import Count
In [30]: Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count('tags')).filter(num_tags=2)
Out[30]: [<Photo: Photo object>]

แบบสอบถามผลลัพธ์:

In [32]: print Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count('tags')).filter(num_tags=2).query
SELECT "test_photo"."id", COUNT("test_photo_tags"."tag_id") AS "num_tags"
FROM "test_photo"
LEFT OUTER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
WHERE ("test_photo_tags"."tag_id" IN (3, 4))
GROUP BY "test_photo"."id", "test_photo"."id"
HAVING COUNT("test_photo_tags"."tag_id") = 2

ANDed Qวัตถุจะไม่ทำงาน:

In [9]: from django.db.models import Q
In [10]: Photo.objects.filter(Q(tags__name='holiday') & Q(tags__name='summer'))
Out[10]: []
In [11]: from operator import and_
In [12]: Photo.objects.filter(reduce(and_, [Q(tags__name='holiday'), Q(tags__name='summer')]))
Out[12]: []

แบบสอบถามผลลัพธ์:

In [25]: print Photo.objects.filter(Q(tags__name='holiday') & Q(tags__name='summer')).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_tag" ON ("test_photo_tags"."tag_id" = "test_tag"."id")
WHERE ("test_tag"."name" = holiday  AND "test_tag"."name" = summer )

6
มีวิธีแก้ไขด้วยการค้นหาแบบกำหนดเองหรือไม่? docs.djangoproject.com/en/1.10/howto/custom-lookups การเปลี่ยน "__in" เป็น "__all" เป็นเรื่องที่ดีและให้สร้างแบบสอบถาม sql ที่ถูกต้อง
t1m0

1
โซลูชันคำอธิบายประกอบนี้ดูเหมือนจะไม่ถูกต้อง เกิดอะไรขึ้นถ้ามีสามแท็กที่เป็นไปได้ (ช่วยให้เรียกอีกหนึ่งสำหรับt3และรูปภาพที่มีแท็กt2และt3จากนั้นรูปนี้จะยังคงตรงกับคำค้นหาที่กำหนด..
beruic

@beruic ฉันคิดว่าความคิดคือคุณจะแทนที่ num_tags = 2 ด้วย num_tags = len (แท็ก); ฉันคาดว่าฮาร์ดโค้ด 2 เป็นเพียงตัวอย่างเท่านั้น
tbm

3
@tbm มันยังคงใช้ไม่ได้ Photo.objects.filter(tags__in=tags)จับคู่รูปภาพที่มีแท็กใด ๆ ไม่ใช่เฉพาะที่มีทั้งหมด บางแท็กที่มีแท็กที่ต้องการเพียงแท็กเดียวอาจมีจำนวนแท็กที่คุณต้องการและแท็กบางแท็กที่มีแท็กที่ต้องการทั้งหมดอาจมีแท็กเพิ่มเติม
beruic

1
@beruic คำอธิบายประกอบจะนับเฉพาะแท็กที่ส่งคืนโดยแบบสอบถามดังนั้นหาก (แท็ก num ส่งคืนโดยการค้นหา) == (แท็ก num ที่ค้นหา) แถวนั้นจะรวมอยู่ด้วย ไม่ได้ค้นหาแท็ก "พิเศษ" ดังนั้นจะไม่ถูกนับ ฉันได้ยืนยันสิ่งนี้ภายในแอปของฉันเอง
tbm

8

อีกวิธีหนึ่งที่ใช้ได้ผลแม้ว่า PostgreSQL เท่านั้นใช้django.contrib.postgres.fields.ArrayField:

ตัวอย่างที่คัดลอกมาจากเอกสาร :

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])

>>> Post.objects.filter(tags__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__contains=['django'])
<QuerySet [<Post: First post>, <Post: Third post>]>

>>> Post.objects.filter(tags__contains=['django', 'thoughts'])
<QuerySet [<Post: First post>]>

ArrayFieldมีคุณสมบัติที่มีประสิทธิภาพมากขึ้นบางอย่างเช่นการซ้อนทับกันและดัชนีการแปลง


3

สิ่งนี้สามารถทำได้โดยการสร้างแบบสอบถามแบบไดนามิกโดยใช้ Django ORM และเวทย์มนตร์ Python :)

from operator import and_
from django.db.models import Q

categories = ['holiday', 'summer']
res = Photo.filter(reduce(and_, [Q(tags__name=c) for c in categories]))

แนวคิดคือการสร้างวัตถุ Q ที่เหมาะสมสำหรับแต่ละประเภทจากนั้นรวมเข้าด้วยกันโดยใช้ตัวดำเนินการ AND เป็นชุด QuerySet เดียว เช่นสำหรับตัวอย่างของคุณมันจะเท่ากับ

res = Photo.filter(Q(tags__name='holiday') & Q(tags__name='summer'))

4
นี้จะไม่ทำงาน ตัวอย่างการค้นหาของคุณจะไม่ส่งคืนอะไรเลยสำหรับโมเดลที่เป็นปัญหา
Davor Lucic

ขอบคุณสำหรับการแก้ไข ฉันคิดว่าการต่อโซ่filterจะเหมือนกับการใช้andสำหรับออบเจ็กต์ Q ในตัวกรองเดียว ... ความผิดพลาดของฉัน
demalexx

ไม่ต้องกังวลความคิดแรกของฉันก็คือวัตถุ Q เช่นกัน
Davor Lucic

1
สิ่งนี้จะช้าลงถ้าคุณทำงานกับตารางขนาดใหญ่และข้อมูลขนาดใหญ่เพื่อเปรียบเทียบ (เช่น 1 ล้านคน)
gies0r

1
วิธีนี้ควรใช้งานได้หากคุณเปลี่ยนจากfilterไปexcludeใช้และใช้ตัวดำเนินการปฏิเสธ ดังนี้: res = Photo.exclude(~reduce(and_, [Q(tags__name=c) for c in categories]))
เบ็น

1

ฉันใช้ฟังก์ชันเล็กน้อยที่วนซ้ำตัวกรองในรายการสำหรับตัวดำเนินการที่กำหนดชื่อคอลัมน์:

def exclusive_in (cls,column,operator,value_list):         
    myfilter = column + '__' + operator
    query = cls.objects
    for value in value_list:
        query=query.filter(**{myfilter:value})
    return query  

และฟังก์ชันนี้สามารถเรียกได้เช่นนั้น:

exclusive_in(Photo,'tags__name','iexact',['holiday','summer'])

มันยังทำงานร่วมกับคลาสและแท็กอื่น ๆ ในรายการ ตัวดำเนินการสามารถเป็นใครก็ได้เช่น 'iexact', 'in', 'contains', 'ne', ...



-1

หากเราต้องการทำแบบไดนามิกให้ทำตามตัวอย่าง:

tag_ids = [t1.id, t2.id]
qs = Photo.objects.all()

for tag_id in tag_ids:
    qs = qs.filter(tag__id=tag_id)    

print qs

ไม่สามารถทำงานได้ทันทีที่มีการทำซ้ำครั้งที่สองชุดแบบสอบถามจะว่างเปล่า
lapin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.