关于python:Django过滤器查询集__in用于列表中的*每个*项目
- 2025-02-13 08:36:00
- admin 原创
- 59
问题描述:
假设我有以下模型
class Photo(models.Model):
tags = models.ManyToManyField(Tag)
class Tag(models.Model):
name = models.CharField(max_length=50)
在视图中,我有一个带有活动过滤器的列表,称为类别。我想过滤类别中所有标签都存在的 Photo 对象。
我试过:
Photo.objects.filter(tags__name__in=categories)
但这与类别中的任何项目相匹配,而不是所有项目。
因此,如果类别是 ['假日','夏季'],我想要带有假日和夏季标签的照片。
这能实现吗?
解决方案 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 )
请注意,每个都会向查询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
edQ
对象将不起作用:
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 )
解决方案 2:
另一种可行的方法是使用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'))
解决方案 4:
我使用了一个小函数,该函数根据给定的运算符和列名对列表进行迭代过滤:
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”等。
解决方案 5:
如果你和我一样被这个问题困扰,而且上面提到的都帮不了你,那么也许这个可以解决你的问题
在某些情况下,最好只存储前一个过滤器的 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)
使用此方法可以帮助您避免JOIN
在 SQL 查询中堆叠:
解决方案 6:
我的解决方案:
假设作者是需要匹配列表中所有项目的元素列表,因此:
for a in author:
queryset = queryset.filter(authors__author_first_name=a)
if not queryset:
break
解决方案 7:
for category in categories:
query = Photo.objects.filter(tags_name=category)
这段代码,过滤掉所有来自类别的标签名称的照片。
解决方案 8:
如果我们想动态地执行此操作,请按照以下示例进行操作:
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
解决方案 9:
queryset = Photo.objects.filter(tags__name="vacaciones") | Photo.objects.filter(tags__name="verano")