목록의 * 모든 * 항목에 대한 Django 필터 쿼리 세트 __in


102

다음 모델이 있다고 가정 해 보겠습니다.

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의 예와 동일합니다). 각각 filterJOIN쿼리 에 더 많은를 추가해야 하므로 너무 많은 경우 주석 접근 방식을 취할 수 있습니다 .
Davor Lucic 2011

1
다음은 문서의 참조입니다. docs.djangoproject.com/en/dev/topics/db/queries/…
sgallen

Django에 의해 이에 대한 내장 함수가있을 것으로 예상 할 수 있습니다.
Vincent

답변:


124

요약:

한 가지 옵션은 주석에서 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, 그리고 사진은 태그가 t2t3그리고이 사진은 여전히 주어진 쿼리와 일치합니다..
beruic

@beruic 아이디어는 num_tags = 2를 num_tags = len (tags); 나는 하드 코딩 된 2가 단지 예일 뿐이라고 생각한다.
tbm

3
@tbm 여전히 작동하지 않습니다. Photo.objects.filter(tags__in=tags)모든 태그가있는 사진뿐만 아니라 태그가있는 사진과 일치합니다. 원하는 태그 중 하나만있는 일부는 찾고있는 태그의 양과 정확히 일치 할 수 있으며 원하는 태그가 모두있는 일부는 추가 태그를 가질 수도 있습니다.
beruic

1
@beruic 주석은 쿼리에서 반환 된 태그 만 계산하므로 (쿼리에서 반환 된 태그 수) == (검색된 태그 수)이면 행이 포함됩니다. "추가"태그는 검색되지 않으므로 계산되지 않습니다. 내 앱에서 이것을 확인했습니다.
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'))

3
이것은 작동하지 않습니다. 쿼리 예제는 문제의 모델에 대해 아무것도 반환하지 않습니다.
Davor Lucic 2011

수정 해 주셔서 감사합니다. 체이닝 filterand하나의 필터에서 Q 객체 를 사용하는 것과 같을 것이라고 생각했습니다 . 내 실수입니다.
demalexx

걱정마, 처음 생각했던 Q도 대상.
Davor Lucic 2011

1
비교할 큰 테이블과 큰 데이터로 작업하면 속도가 느려집니다. (각각 1 백만)
gies0r

이 방법은 해야 당신이 전환하는 경우 작업 filterexclude하고 부정 연산자를 사용합니다. 다음과 같이 : res = Photo.exclude(~reduce(and_, [Q(tags__name=c) for c in categories]))
Ben

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.