[django] Django에서 여러 filter () 연결, 이것이 버그입니까?
저는 항상 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 () …”사용에 대한 단점이 있습니까? 두 쿼리 세트가 동일해야 함을 나타냅니다.
답변
내가 이해하는 방식은 그것들이 디자인에 따라 미묘하게 다르다는 것입니다 (그리고 나는 확실히 수정을 위해 열려 있습니다) : 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)로 제한했습니다. 두 번째 필터에서 선택한 항목은 첫 번째 필터의 항목과 같을 수도 있고 같지 않을 수도 있습니다 .`
답변
이 두 스타일의 필터링은 대부분의 경우 동일하지만 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로 변경되었습니다.
답변
생성 된 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 를 읽으십시오.
답변
때때로 다음과 같이 여러 필터를 결합하고 싶지 않습니다.
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)
이 솔루션은 대규모 쿼리 세트에서도 저렴합니다.
도움이 되었기를 바랍니다.