Chuỗi nhiều bộ lọc () trong Django, đây có phải là lỗi không?


103

Tôi luôn cho rằng việc xâu chuỗi nhiều lệnh gọi filter () trong Django luôn giống như việc thu thập chúng trong một lệnh gọi.

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

nhưng tôi đã gặp phải một bộ truy vấn phức tạp trong mã của mình, nơi đây không phải là trường hợp

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 được tạo là

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 )

Bộ truy vấn đầu tiên với các filter()lệnh gọi chuỗi tham gia mô hình Khoảng không quảng cáo hai lần một cách hiệu quả tạo OR giữa hai điều kiện trong khi bộ truy vấn thứ hai AND kết hợp hai điều kiện với nhau. Tôi đã mong đợi rằng truy vấn đầu tiên cũng sẽ VÀ hai điều kiện. Đây có phải là hành vi được mong đợi hay đây là một lỗi trong Django?

Câu trả lời cho câu hỏi liên quan Có nhược điểm khi sử dụng ".filter (). Filter (). Filter () ..." trong Django không? dường như chỉ ra rằng hai bộ truy vấn phải tương đương nhau.

Câu trả lời:


117

Theo cách hiểu của tôi là chúng khác nhau một cách tinh tế theo thiết kế (và tôi chắc chắn đang mở để điều chỉnh): filter(A, B)đầu tiên sẽ lọc theo A và sau đó lọc con theo B, trong khi filter(A).filter(B)sẽ trả về một hàng phù hợp với A 'và' có khả năng khác hàng phù hợp với B.

Hãy xem ví dụ ở đây:

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

đặc biệt:

Mọi thứ bên trong một lệnh gọi filter () duy nhất được áp dụng đồng thời để lọc ra các mục phù hợp với tất cả các yêu cầu đó. Các lệnh gọi bộ lọc kế tiếp () hạn chế hơn nữa tập hợp các đối tượng

...

Trong ví dụ thứ hai này (bộ lọc (A) .filter (B)), bộ lọc đầu tiên đã hạn chế bộ truy vấn thành (A). Bộ lọc thứ hai đã hạn chế nhóm các blog hơn nữa đối với những blog cũng là (B). Các mục nhập được chọn bởi bộ lọc thứ hai có thể giống hoặc không giống với các mục nhập trong bộ lọc đầu tiên. '


18
Hành vi này, mặc dù được ghi lại, dường như vi phạm nguyên tắc ít gây ngạc nhiên nhất. Nhiều AND của bộ lọc () kết hợp với nhau khi các trường trên cùng một mô hình, nhưng sau đó OR kết hợp với nhau khi mở rộng các mối quan hệ.
gerdemb,

3
Tôi tin rằng bạn đã hiểu sai ở đoạn đầu tiên - bộ lọc (A, B) là tình huống VÀ ('lennon' AND 2008 trong tài liệu), trong khi bộ lọc (A) .filter (B) là tình huống HOẶC ( 'lennon' HOẶC 2008). Điều này có ý nghĩa khi bạn nhìn vào các truy vấn được tạo trong câu hỏi - trường hợp .filter (A) .filter (B) tạo các phép nối hai lần, dẫn đến một OR.
Sam

17
bộ lọc (A, B) là bộ lọc VÀ (A). bộ lọc (B) là HOẶC
WeizhongTu

3
như vậy further restrictcó nghĩa là less restrictivegì?
boh

7
Câu trả lời này không chính xác. Nó không phải là "HOẶC". Câu này "Bộ lọc thứ hai đã hạn chế nhóm các blog hơn nữa đối với những blog cũng là (B)." đề cập rõ ràng "đó cũng là (B)." Nếu bạn quan sát thấy một hành vi tương tự như HOẶC trong ví dụ cụ thể này, điều đó không nhất thiết có nghĩa là bạn có thể khái quát hóa cách diễn giải của riêng mình. Hãy xem câu trả lời của "Kevin 3112" và "Johnny Tsang." Tôi tin rằng đó là những câu trả lời chính xác.
1man,

66

Hai kiểu lọc này tương đương nhau trong hầu hết các trường hợp, nhưng khi truy vấn trên các đối tượng dựa trên ForeignKey hoặc ManyToManyField, chúng hơi khác nhau.

Ví dụ từ tài liệu .

mô hình
Blog to Entry là mối quan hệ một-nhiều.

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

Các đối tượng
Giả sử có một số đối tượng blog và entry ở đây.
nhập mô tả hình ảnh ở đây

truy vấn

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)  

Đối với truy vấn đầu tiên (một bộ lọc duy nhất), nó chỉ khớp với blog1.

Đối với truy vấn thứ 2 (bộ lọc chuỗi một), nó lọc ra blog1 và blog2.
Bộ lọc đầu tiên hạn chế bộ truy vấn ở blog1, blog2 và blog5; bộ lọc thứ hai hạn chế tập hợp các blog hơn nữa đối với blog1 và blog2.

Và bạn nên nhận ra rằng

Chúng tôi đang lọc các mục Blog với mỗi câu lệnh lọc, không phải các mục Mục nhập.

Vì vậy, nó không giống nhau, bởi vì Blog và Entry là mối quan hệ đa giá trị.

Tham khảo: https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships
Nếu có gì sai, vui lòng sửa cho tôi.

Chỉnh sửa: Đã thay đổi v1.6 thành v1.8 vì liên kết 1.6 không còn nữa.


3
Bạn dường như bị lẫn lộn giữa "phù hợp" và "lọc ra". Nếu bạn mắc kẹt với "truy vấn này trả về", nó sẽ rõ ràng hơn rất nhiều.
OrangeDog

7

Như bạn có thể thấy trong các câu lệnh SQL được tạo, sự khác biệt không phải là "OR" như một số người có thể nghi ngờ. Đó là cách đặt WHERE và JOIN.

Ví dụ1 (cùng một bảng được tham gia):

(ví dụ từ 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)

Điều này sẽ cung cấp cho bạn tất cả các Blog có một mục nhập với cả hai (entry_ headline _contains = 'Lennon') VÀ (entry__pub_date__year = 2008), đó là những gì bạn mong đợi từ truy vấn này. Kết quả: Đặt chỗ với {entry.headline: 'Life of Lennon', entry.pub_date: '2008'}

Ví dụ 2 (chuỗi)

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

Điều này sẽ bao gồm tất cả các kết quả từ Ví dụ 1, nhưng nó sẽ tạo ra nhiều kết quả hơn một chút. Bởi vì trước tiên nó lọc tất cả các blog có (entry_ headline _contains = 'Lennon') và sau đó từ các bộ lọc kết quả (entry__pub_date__year = 2008).

Sự khác biệt là nó cũng sẽ cung cấp cho bạn các kết quả như: Đặt phòng với {entry.headline: ' Lennon ', entry.pub_date: 2000}, {entry.headline: 'Bill', entry.pub_date: 2008 }

Trong trường hợp của bạn

Tôi nghĩ đây là cái bạn cần:

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

Và nếu bạn muốn sử dụng HOẶC, vui lòng đọc: https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects


Ví dụ thứ hai không thực sự đúng. Tất cả các bộ lọc chuỗi được áp dụng cho các đối tượng được truy vấn, tức là chúng được AND cùng nhau trong truy vấn.
Janne

Tôi tin rằng Ví dụ 2 là đúng và nó thực sự là một lời giải thích được lấy từ các tài liệu chính thức của Django, như được tham chiếu. Tôi có thể không phải là người giải thích tốt nhất và tôi xin lỗi vì điều đó. Ví dụ 1 là một AND trực tiếp như bạn mong đợi trong cách viết SQL thông thường. Ví dụ 1 đưa ra một cái gì đó như thế này: 'CHỌN mục nhập blog THAM GIA WHERE entry.head_line LIKE " Lennon " AND entry.year == 2008 Ví dụ 2 đưa ra một cái gì đó như sau:' CHỌN mục nhập blog THAM GIA WHERE entry.head_list LIKE " Lennon " UNION CHỌN blog THAM GIA mục nhập WHERE entry.head_list THÍCH " Lennon " '
Johnny Tsang

Thưa bạn, bạn khá đúng. Vì vội vàng, tôi đã bỏ lỡ thực tế rằng tiêu chí lọc của chúng tôi đang hướng đến mối quan hệ một-nhiều, không phải chính blog.
Janne

0

Đôi khi bạn không muốn kết hợp nhiều bộ lọc với nhau như thế này:

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

Và đoạn mã sau thực sự sẽ không trả về điều chính xác.

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

Những gì bạn có thể làm bây giờ là sử dụng bộ lọc số lượng chú thích.

Trong trường hợp này, chúng tôi tính tất cả các ca thuộc về một sự kiện nhất định.

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

Sau đó, bạn có thể lọc theo chú thích.

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

Giải pháp này cũng rẻ hơn trên các bộ truy vấn lớn.

Hi vọng điêu nay co ich.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.