Djangoフィルタークエリセット__inリストの*すべての*アイテム


101

次のモデルがあるとしましょう

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)

ただし、これはすべてのアイテムではなく、カテゴリ内のすべてのアイテムに一致ます。

したがって、カテゴリが['holiday'、 'summer']になる場合は、休日と夏の両方のタグが付けられたPhotoが必要です。

これは達成できますか?


6
多分: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

1
これがドキュメント内のリファレンスです:docs.djangoproject.com/en/dev/topics/db/queries/…– sgallen '25
12/25

Djangoによるこのための
Vincent

回答:


124

概要:

1つのオプションは、コメントで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 )

それぞれfilterJOINSクエリにさらに追加することに注意してください。

注釈 アプローチの使用:

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 2017年

1
この注釈ソリューションは間違っているようです。可能な3つのタグがある場合、どのようなこと(のための追加的な1呼び出すことができますt3、そして写真は、タグを持っているt2t3。。そして、この写真はまだ与えられたクエリに一致します
beruic

@beruic考えは、num_tags = 2をnum_tags = len(tags);に置き換えることだと思います。ハードコードされた2は単なる例であることを期待しています。
tbm

3
@tbmそれでも動作しません。Photo.objects.filter(tags__in=tags)すべてのタグが付いている写真だけでなく、いずれかのタグが付いている写真に一致します。目的のタグの1つだけがあり、探しているタグの量が正確に含まれているタグや、必要なタグがすべて含まれているタグの一部に、追加のタグがある場合があります。
18年

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演算子を使用してそれらを1つのクエリセットに結合することです。たとえば、あなたの例では、

res = Photo.filter(Q(tags__name='holiday') & Q(tags__name='summer'))

3
これは機能しません。クエリの例では、問題のモデルについては何も返されません。
Davor Lucic

訂正ありがとうございます。チェーニングfilterand1つのフィルターでQオブジェクトに使用するのと同じだと思いました...私の間違い。
demalexx

心配しないで、Qも反対する私の最初の考え。
Davor Lucic、2011

1
比較する大きなテーブルと大きなデータを操作する場合、これは遅くなります。(それぞれ100万のように)
gies0r 2018年

このアプローチ、からfilterに切り替えexcludeて否定演算子を使用する場合に機能します。このように: res = Photo.exclude(~reduce(and_, [Q(tags__name=c) for c in categories]))
ベン

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

2回目の反復ですぐに機能しなくなり、クエリセットが空になります
lapin
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.