列表中每个*项目的Django过滤器查询集__in


101

假设我有以下型号

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'],则我希望Photo带有假日和夏季标签。

能做到吗?


6
也许:qs = Photo.objects.all(); 用于类别中的类别:qs = qs.filter(tags__name = category)
jpic 2011年

2
jpic是正确的,Photo.objects.filter(tags__name='holiday').filter(tags__name='summer')是要走的路。(这与jpic的示例相同)。每个查询filter都应添加更多JOINs,因此如果它们太多,则可以采用注释方法
Davor Lucic


你可能会认为那里是一个内置的由Django的功能此
文森特

Answers:


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 18'Apr

@beruic我认为您的想法是将num_tags = 2替换为num_tags = len(tags); 我希望硬编码2只是为了举例。
tbm

3
@tbm它仍然无法正常工作。Photo.objects.filter(tags__in=tags)匹配具有任何标签的照片,而不仅仅是具有全部标签的照片。其中一些仅具有所需标签之一的标签,可能恰好具有您要查找的标签数量,而某些具有所有所需标签的标签中的某些标签可能还具有其他标签。
比利时

1
@beruic注释仅计算查询返回的标签,因此,如果(查询返回的num个标签)==(搜索的num个标签),则包含该行;不会搜索“额外”标签,因此不会被计算在内。我已经在自己的应用中对此进行了验证。
tbm

8

尽管仅适用于PostgreSQL,另一种有效的方法是使用django.contrib.postgres.fields.ArrayField

docs复制的示例:

>>> 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
这行不通。您的查询示例将不为所讨论的模型返回任何内容。
达沃·卢西奇

感谢您的指正。我认为链接filter将与and在一个过滤器中用于Q对象相同...我的错。
demalexx 2011年

不用担心,我首先想到的地方还有Q对象。
Davor Lucic

1
如果您使用大型表和大型数据进行比较,这会使我们变慢。(例如每百万)
gies0r

如果您从切换到并使用求反运算符,则此方法应该有效。像这样: filterexcluderes = 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

不能在第二次迭代后立即运行,查询集将为空
lapin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.