1. ホーム
  2. python

[解決済み] Django filter queryset __in for *every* item in list

2022-07-21 08:43:14

質問

次のようなモデルがあるとします。

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'] の場合、holiday と summer の両方のタグを持つ写真が欲しいのです。

これは実現できるのでしょうか?

どのように解決するのですか?

概要です。

コメントでjpicとsgallenが提案したように、1つのオプションとして .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

ANDQ オブジェクトは動作しないでしょう。

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 )