簡體   English   中英

Django 過濾器查詢集 __in 用於列表中的*每個*項目

[英]Django filter queryset __in for *every* item in list

假設我有以下型號

class Photo(models.Model):
    tags = models.ManyToManyField(Tag)

class Tag(models.Model):
    name = models.CharField(max_length=50)

在一個視圖中,我有一個名為category 的活動過濾器列表。 我想篩選具有所有標簽呈現種類照片的對象。

我試過:

Photo.objects.filter(tags__name__in=categories)

但這匹配類別中的任何項目,而不是所有項目。

因此,如果類別是 ['holiday', 'summer'] 我想要帶有假日和夏季標簽的照片。

這能實現嗎?

概括:

一種選擇是,正如 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

AND ed 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 )

另一種有效的方法,雖然僅適用於 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具有一些更強大的功能,例如重疊索引轉換

這也可以通過使用 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'))

如果你像我一樣在這個問題上掙扎,但沒有提到對你有幫助,也許這個可以解決你的問題

在某些情況下,最好只存儲先前過濾器的 id,而不是鏈接過濾器

tags = [1, 2]
for tag in tags:
    ids = list(queryset.filter(tags__id=tag).values_list("id", flat=True))
    queryset = queryset.filter(id__in=ids)

使用這種方法將幫助您避免在 SQL 查詢中堆疊JOIN

我使用了一個小函數,它對給定運算符 a 列名的列表迭代過濾器:

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',...

我的解決方案:假設作者是需要匹配列表中所有項目的元素列表,所以:

        for a in author:
            queryset = queryset.filter(authors__author_first_name=a)
                if not queryset:
                    break

如果我們想動態地做到這一點,請遵循示例:

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
queryset = Photo.objects.filter(tags__name="vacaciones") | Photo.objects.filter(tags__name="verano")

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM