การเชื่อมโยงหลายตัวกรอง () ใน Django นี่เป็นข้อบกพร่องหรือไม่?


114

ฉันคิดเสมอว่าการเชื่อมโยงการโทรหลายตัวกรอง () ใน Django นั้นเหมือนกับการรวบรวมในการโทรครั้งเดียวเสมอ

# Equivalent
Model.objects.filter(foo=1).filter(bar=2)
Model.objects.filter(foo=1,bar=2)

แต่ฉันได้พบกับ Queryset ที่ซับซ้อนในรหัสของฉันซึ่งไม่เป็นเช่นนั้น

class Inventory(models.Model):
    book = models.ForeignKey(Book)

class Profile(models.Model):
    user = models.OneToOneField(auth.models.User)
    vacation = models.BooleanField()
    country = models.CharField(max_length=30)

# Not Equivalent!
Book.objects.filter(inventory__user__profile__vacation=False).filter(inventory__user__profile__country='BR')
Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')

SQL ที่สร้างขึ้นคือ

SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") INNER JOIN "library_inventory" T5 ON ("library_book"."id" = T5."book_id") INNER JOIN "auth_user" T6 ON (T5."user_id" = T6."id") INNER JOIN "library_profile" T7 ON (T6."id" = T7."user_id") WHERE ("library_profile"."vacation" = False  AND T7."country" = BR )
SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") WHERE ("library_profile"."vacation" = False  AND "library_profile"."country" = BR )

แบบสอบถามชุดแรกที่มีการfilter()โทรที่ถูกล่ามไว้จะรวมโมเดลสินค้าคงคลังสองครั้งอย่างมีประสิทธิภาพในการสร้าง OR ระหว่างเงื่อนไขทั้งสองในขณะที่ชุดแบบสอบถามที่สองและเงื่อนไขทั้งสองเข้าด้วยกัน ฉันคาดหวังว่าแบบสอบถามแรกจะเป็นและเงื่อนไขทั้งสอง นี่เป็นพฤติกรรมที่คาดหวังหรือเป็นข้อผิดพลาดใน Django?

คำตอบสำหรับคำถามที่เกี่ยวข้องมีข้อเสียในการใช้ ".filter (). filter (). filter () ... " ใน Django หรือไม่? ดูเหมือนจะระบุว่าทั้งสองชุดข้อมูลควรจะเท่ากัน

คำตอบ:


123

วิธีที่ฉันเข้าใจคือมันแตกต่างกันอย่างละเอียดโดยการออกแบบ (และฉันก็เปิดให้แก้ไขอย่างแน่นอน): filter(A, B)ก่อนอื่นจะกรองตาม A แล้วกรองย่อยตาม B ในขณะที่filter(A).filter(B)จะส่งคืนแถวที่ตรงกับ A 'และ' ซึ่งอาจแตกต่างกัน แถวที่ตรงกับ B

ดูตัวอย่างที่นี่:

https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships

โดยเฉพาะ:

ทุกสิ่งภายในการเรียกตัวกรองเดียว () จะถูกนำไปใช้พร้อมกันเพื่อกรองรายการที่ตรงกับข้อกำหนดเหล่านั้นทั้งหมด ตัวกรองลำดับต่อเนื่อง () เรียกการ จำกัด ชุดของวัตถุเพิ่มเติม

...

ในตัวอย่างที่สองนี้ (ตัวกรอง (A) .filter (B)) ตัวกรองแรก จำกัด ชุดแบบสอบถามไว้ที่ (A) ตัวกรองที่สอง จำกัด ชุดของบล็อกไว้เฉพาะกับบล็อกที่เป็น (B) รายการที่เลือกโดยตัวกรองที่สองอาจจะใช่หรือไม่เหมือนกับรายการในตัวกรองแรก


20
พฤติกรรมนี้แม้จะมีการบันทึกไว้ แต่ดูเหมือนว่าจะละเมิดหลักการของความประหลาดใจน้อยที่สุด ตัวกรองหลายตัวและรวมกันเมื่อฟิลด์อยู่ในแบบจำลองเดียวกัน แต่ตามด้วย OR ร่วมกันเมื่อขยายความสัมพันธ์
gerdemb

3
ฉันเชื่อว่าคุณเข้าใจผิดในย่อหน้าแรก - ตัวกรอง (A, B) คือสถานการณ์ AND ('lennon' และ 2008 ในเอกสาร) ในขณะที่ตัวกรอง (A) .filter (B) คือสถานการณ์หรือ ( 'lennon' หรือ 2008) สิ่งนี้สมเหตุสมผลเมื่อคุณดูการสืบค้นที่สร้างขึ้นในคำถาม - กรณี. filter (A) .filter (B) จะสร้างการรวมสองครั้งทำให้เกิด OR
แซม

17
ตัวกรอง (A, B) คือตัวกรอง AND (A) ตัวกรอง (B) คือหรือ
WeizhongTu

3
จึงfurther restrictหมายถึงless restrictive?
boh

7
คำตอบนี้ไม่ถูกต้อง ไม่ใช่ "หรือ" ประโยคนี้ "ตัวกรองที่สอง จำกัด ชุดของบล็อกไว้สำหรับบล็อกที่เป็น (B) ด้วย" กล่าวถึง "นั่นคือ (B)" อย่างชัดเจน หากคุณสังเกตเห็นพฤติกรรมที่คล้ายกับ OR ในตัวอย่างนี้ไม่จำเป็นต้องหมายความว่าคุณสามารถสรุปการตีความของคุณเองได้ โปรดดูคำตอบของ "Kevin 3112" และ "Johnny Tsang" ฉันเชื่อว่านั่นคือคำตอบที่ถูกต้อง
1 น

74

รูปแบบการกรองทั้งสองนี้มีความเท่าเทียมกันในกรณีส่วนใหญ่ แต่เมื่อค้นหาวัตถุบน ForeignKey หรือ ManyToManyField จะแตกต่างกันเล็กน้อย

ตัวอย่างจากเอกสาร

Model
Blog to Entry เป็นความสัมพันธ์แบบหนึ่งต่อกลุ่ม

from django.db import models

class Blog(models.Model):
    ...

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    pub_date = models.DateField()
    ...

วัตถุ
สมมติว่ามีบล็อกและวัตถุรายการอยู่ที่นี่
ป้อนคำอธิบายภาพที่นี่

แบบสอบถาม

Blog.objects.filter(entry__headline_contains='Lennon', 
    entry__pub_date__year=2008)
Blog.objects.filter(entry__headline_contains='Lennon').filter(
    entry__pub_date__year=2008)  
    

สำหรับข้อความค้นหาที่ 1 (ตัวกรองเดียว) จะตรงกับบล็อก 1 เท่านั้น

สำหรับแบบสอบถามที่ 2 (ตัวกรองแบบล่ามโซ่หนึ่ง) จะกรองบล็อก 1 และบล็อก 2 ออก
ตัวกรองแรก จำกัด ชุดแบบสอบถามไว้ที่ blog1, blog2 และ blog5 ตัวกรองที่สอง จำกัด ชุดของบล็อกไว้ที่ blog1 และ blog2

และคุณควรตระหนักว่า

เรากำลังกรองรายการบล็อกด้วยคำสั่งตัวกรองแต่ละรายการไม่ใช่รายการรายการ

ดังนั้นจึงไม่เหมือนกันเนื่องจาก Blog และ Entry เป็นความสัมพันธ์ที่มีหลายมูลค่า

อ้างอิง: https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships
หากมีสิ่งผิดปกติโปรดแก้ไขฉัน

แก้ไข: เปลี่ยน v1.6 เป็น v1.8 เนื่องจากลิงก์ 1.6 ไม่สามารถใช้งานได้อีกต่อไป


3
ดูเหมือนคุณจะผสมกันระหว่าง "การจับคู่" และ "ตัวกรอง" หากคุณติดอยู่กับ "คำค้นหานี้ส่งกลับ" มันจะชัดเจนกว่านี้มาก
OrangeDog

7

ดังที่คุณเห็นในคำสั่ง SQL ที่สร้างขึ้นความแตกต่างไม่ใช่ "OR" อย่างที่บางคนอาจสงสัย มันเป็นวิธีการวางตำแหน่งและเข้าร่วม

ตัวอย่างที่ 1 (ตารางรวมเดียวกัน):

(ตัวอย่างจากhttps://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships )

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

สิ่งนี้จะให้บล็อกทั้งหมดที่มีรายการเดียวกับทั้ง (entry_ headline _contains = 'Lennon') AND (entry__pub_date__year = 2008) ซึ่งเป็นสิ่งที่คุณคาดหวังจากข้อความค้นหานี้ ผลลัพธ์: จองกับ {entry.headline: 'Life of Lennon', entry.pub_date: '2008'}

ตัวอย่างที่ 2 (ถูกล่ามโซ่)

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

ซึ่งจะครอบคลุมผลลัพธ์ทั้งหมดจากตัวอย่างที่ 1 แต่จะให้ผลลัพธ์มากกว่าเล็กน้อย เนื่องจากขั้นแรกจะกรองบล็อกทั้งหมดด้วย (entry_ headline _contains = 'Lennon') และจากตัวกรองผลลัพธ์ (entry__pub_date__year = 2008)

ความแตกต่างก็คือมันจะให้ผลลัพธ์เช่น: จองด้วย {entry.headline: ' Lennon ', entry.pub_date: 2000}, {entry.headline: 'Bill', entry.pub_date: 2008 }

ในกรณีของคุณ

ฉันคิดว่านี่คือสิ่งที่คุณต้องการ:

Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')

และหากคุณต้องการใช้หรือโปรดอ่าน: https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects


ตัวอย่างที่สองไม่เป็นความจริง ตัวกรองที่ถูกล่ามโซ่ทั้งหมดจะถูกนำไปใช้กับอ็อบเจ็กต์ที่ถูกสืบค้นกล่าวคือตัวกรองเหล่านี้จะเป็น AND พร้อมกันในแบบสอบถาม
เจนส์

ฉันเชื่อว่าตัวอย่างที่ 2 ถูกต้องและเป็นคำอธิบายที่นำมาจากเอกสาร Django อย่างเป็นทางการตามที่อ้างถึง ฉันอาจไม่ใช่คนอธิบายได้ดีที่สุดและฉันก็ให้อภัยสำหรับเรื่องนั้น ตัวอย่างที่ 1 คือโดยตรงและตามที่คุณคาดหวังในการเขียน SQL ปกติ ตัวอย่างที่ 1 ให้ข้อมูลดังนี้ 'SELECT blog JOIN entry WHERE entry.head_line LIKE " Lennon " AND entry.year == 2008 ตัวอย่างที่ 2 ให้ข้อมูลดังนี้' SELECT blog JOIN entry WHERE entry.head_list LIKE " Lennon " UNION SELECT blog เข้าร่วมรายการ WHERE entry.head_list เช่น " Lennon " '
Johnny Tsang

เซอร์คุณค่อนข้างถูกต้อง ด้วยความรีบร้อนฉันพลาดข้อเท็จจริงที่ว่าเกณฑ์การกรองของเราชี้ไปที่ความสัมพันธ์แบบหนึ่งต่อกลุ่มไม่ใช่ที่ตัวบล็อก
เจนส์

2

จากDjango docs :

เพื่อจัดการทั้งสองสถานการณ์นี้ Django มีวิธีการประมวลผลการเรียกตัวกรอง () ที่สอดคล้องกัน ทุกสิ่งภายในการเรียกตัวกรองเดียว () จะถูกนำไปใช้พร้อมกันเพื่อกรองรายการที่ตรงกับข้อกำหนดเหล่านั้นทั้งหมด การเรียกใช้ตัวกรองลำดับต่อเนื่อง () จะ จำกัด ชุดของอ็อบเจ็กต์ต่อไป แต่สำหรับความสัมพันธ์แบบหลายค่าจะใช้กับอ็อบเจ็กต์ใด ๆ ที่เชื่อมโยงกับโมเดลหลักไม่จำเป็นต้องเป็นอ็อบเจ็กต์ที่ถูกเลือกโดยการเรียกตัวกรอง () ก่อนหน้านี้

  • มีการกล่าวอย่างชัดเจนว่ามีการใช้เงื่อนไขหลายเงื่อนไขในข้อเดียวfilter()พร้อมกัน นั่นหมายความว่าการทำ:
objs = Mymodel.objects.filter(a=True, b=False)

จะกลับชุดข้อความค้นหาที่มี RAWs จากแบบจำลองMymodelที่และa=True b=False

  • ต่อเนื่องfilter()กันในบางกรณีจะให้ผลลัพธ์เดียวกัน ทำ:
objs = Mymodel.objects.filter(a=True).filter(b=False)

จะส่งคืนชุดข้อมูลดิบจากโมเดลMymodelโดยที่a=True AND b=Falseด้วย เนื่องจากคุณได้รับแบบสอบถาม "ครั้งแรก" ที่มีระเบียนซึ่งมีa=Trueแล้วจึง จำกัด เฉพาะผู้ที่มีb=Falseในเวลาเดียวกัน

  • ความแตกต่างในการผูกมัดfilter()เกิดขึ้นเมื่อมีmulti-valued relationsซึ่งหมายความว่าคุณกำลังใช้โมเดลอื่น ๆ (เช่นตัวอย่างที่ให้ไว้ในเอกสารระหว่างโมเดลบล็อกและรายการ) ว่ากันว่าในกรณีนี้(...) they apply to any object linked to the primary model, not necessarily those objects that were selected by an earlier filter() call.

ซึ่งหมายความว่าจะใช้สิ่งต่อเนื่องfilter()กับโมเดลเป้าหมายโดยตรงไม่ใช่ก่อนหน้าfilter()

ถ้าฉันนำตัวอย่างจากเอกสาร:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

จำไว้ว่าเป็นโมเดลBlogที่ถูกกรองไม่ใช่ไฟล์Entry. ดังนั้นมันจะถือว่าทั้ง 2 filter()เป็นอิสระ

ตัวอย่างเช่นจะส่งคืนแบบสอบถามกับบล็อกที่มีรายการที่มี 'เลนนอน' (แม้ว่าจะไม่ได้มาจากปี 2008) และรายการที่มาจากปี 2008 (แม้ว่าบรรทัดแรกของพวกเขาจะไม่มี 'เลนนอน' ก็ตาม)

คำตอบนี้จะยิ่งไปกว่านั้นในคำอธิบาย และคำถามเดิมก็คล้ายกัน


0

บางครั้งคุณไม่ต้องการรวมตัวกรองหลายตัวเข้าด้วยกันเช่นนี้:

def your_dynamic_query_generator(self, event: Event):
    qs \
    .filter(shiftregistrations__event=event) \
    .filter(shiftregistrations__shifts=False)

และรหัสต่อไปนี้จะไม่ส่งคืนสิ่งที่ถูกต้อง

def your_dynamic_query_generator(self, event: Event):
    return Q(shiftregistrations__event=event) & Q(shiftregistrations__shifts=False)

สิ่งที่ทำได้ตอนนี้คือใช้ตัวกรองการนับคำอธิบายประกอบ

ในกรณีนี้เราจะนับการเปลี่ยนแปลงทั้งหมดที่เป็นของเหตุการณ์หนึ่ง

qs: EventQuerySet = qs.annotate(
    num_shifts=Count('shiftregistrations__shifts', filter=Q(shiftregistrations__event=event))
)

หลังจากนั้นคุณสามารถกรองตามคำอธิบายประกอบ

def your_dynamic_query_generator(self):
    return Q(num_shifts=0)

โซลูชันนี้ยังถูกกว่าในชุดแบบสอบถามขนาดใหญ่

หวังว่านี่จะช่วยได้

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