Django에서 여러 filter () 연결, 이것이 버그입니까?


103

저는 항상 Django에서 여러 filter () 호출을 연결하는 것이 단일 호출로 수집하는 것과 항상 동일하다고 가정했습니다.

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

하지만 나는 이것이 사실이 아닌 내 코드에서 복잡한 쿼리 세트를 실행했습니다.

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을 생성하는 반면 두 번째 쿼리 세트는 두 조건을 함께 AND로 만듭니다. 첫 번째 쿼리와 두 가지 조건이있을 것으로 예상했습니다. 이것이 예상되는 동작입니까 아니면 Django의 버그입니까?

관련 질문에 대한 답변 Django에서 ".filter (). filter (). filter () ..."사용에 대한 단점이 있습니까? 두 쿼리 세트가 동일해야 함을 나타냅니다.

답변:


117

내가 이해하는 방식은 그것들이 디자인에 따라 미묘하게 다르다는 것입니다 (그리고 나는 확실히 수정을 위해 열려 있습니다) : filter(A, B)먼저 A에 따라 필터링 한 다음 B에 따라 하위 필터링하고, A와 filter(A).filter(B)일치하는 행을 반환하고 잠재적으로 다른 행을 반환합니다. B와 일치하는 행.

여기에서 예를보십시오.

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

특별히:

단일 filter () 호출 내의 모든 것이 동시에 적용되어 모든 요구 사항과 일치하는 항목을 필터링합니다. 연속적인 filter () 호출은 객체 세트를 더욱 제한합니다.

...

이 두 번째 예제 (filter (A) .filter (B))에서 첫 번째 필터는 쿼리 세트를 (A)로 제한했습니다. 두 번째 필터는 블로그 세트를 (B)로 제한했습니다. 두 번째 필터에서 선택한 항목은 첫 번째 필터의 항목과 같을 수도 있고 같지 않을 수도 있습니다 .`


18
이 행동은 문서화되어 있지만 최소 경악의 원칙을 위반하는 것으로 보입니다. 필드가 동일한 모델에 있으면 여러 filter ()의 AND가 함께 있지만 관계를 확장 할 때는 OR이 함께합니다.
gerdemb

3
첫 번째 단락에서 잘못된 방법이 있다고 생각합니다. filter (A, B)는 AND 상황 (문서의 'lennon'AND 2008)이고, filter (A) .filter (B)는 OR 상황입니다 ( '레논'OR 2008). 이것은 질문에서 생성 된 쿼리를 볼 때 의미가 있습니다. .filter (A) .filter (B) 케이스는 조인을 두 번 생성하여 OR을 생성합니다.

17
filter (A, B)는 AND filter (A) .filter (B)는 OR
WeizhongTu

3
그래서 further restrict의미 less restrictive?

7
이 대답은 틀 렸습니다. "OR"이 아닙니다. 이 문장은 "두 번째 필터는 블로그 모음을 (B) 인 블로그로 제한했습니다." "그것도 (B)"라고 명확하게 언급합니다. 이 특정 예에서 OR와 유사한 동작을 관찰한다고해서 반드시 자신의 해석을 일반화 할 수 있다는 의미는 아닙니다. "Kevin 3112"와 "Johnny Tsang"의 답변을보십시오. 나는 그것이 정답이라고 믿습니다.
1 인

66

이 두 스타일의 필터링은 대부분의 경우 동일하지만 ForeignKey 또는 ManyToManyField를 기반으로하는 개체에 대한 쿼리의 경우 약간 다릅니다.

문서의 예 .

모델
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)의 경우 blog1 과만 일치합니다.

두 번째 쿼리 (체인 필터 1)의 경우 blog1 및 blog2를 필터링합니다.
첫 번째 필터는 queryset을 blog1, blog2 및 blog5로 제한합니다. 두 번째 필터는 블로그 세트를 blog1 및 blog2로 더 제한합니다.

그리고 당신은

항목 항목이 아닌 각 필터 문으로 블로그 항목을 필터링합니다.

따라서 Blog와 Entry는 다중 값 관계이기 때문에 동일하지 않습니다.

참조 : https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships
잘못된 점이 있으면 정정 해주세요.

편집 : 1.6 링크를 더 이상 사용할 수 없기 때문에 v1.6이 v1.8로 변경되었습니다.


3
당신은 "일치"와 "필터 아웃"사이에 섞여있는 것 같습니다. "이 쿼리가 반환 됨"을 고수하면 훨씬 더 명확해질 것입니다.
OrangeDog

7

생성 된 SQL 문에서 볼 수 있듯이 일부는 의심 할 수있는 "OR"이 아닙니다. WHERE 및 JOIN이 배치되는 방법입니다.

예 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')과 (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)에서 필터링하기 때문입니다.

차이점은 다음과 같은 결과도 제공한다는 것입니다. Book with {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로 연결됩니다.
Janne

나는 예제 2가 옳다고 믿으며 실제로 참조 된 것처럼 공식 Django 문서에서 가져온 설명입니다. 내가 최고의 설명자가 아닐 수도 있고 용서합니다. 예 1은 일반 SQL 작성에서 예상하는 직접 AND입니다. 예제 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 블로그 JOIN entry WHERE entry.head_list LIKE " Lennon " "
Johnny Tsang

선생님 말이 맞습니다. 서둘러 필터링 기준이 블로그 자체가 아니라 일대 다 관계를 가리키고 있다는 사실을 놓쳤습니다.
Janne 2014 년

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.