ChatGPT解决这个技术问题 Extra ChatGPT

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

假设我有以下模型

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'] 我想要带有假期和夏季标签的照片。

这可以实现吗?

也许:qs=Photo.objects.all();对于类别中的类别:qs = qs.filter(tags__name=category)
jpic 是正确的,Photo.objects.filter(tags__name='holiday').filter(tags__name='summer') 是要走的路。 (这与 jpic 的示例相同)。每个 filter 应该添加更多的 JOIN 进行查询,因此如果它们太多,您可以使用 annotation approach
你会期望 Django 有一个内置函数

H
Hedde van der Heide

概括:

正如 jpic 和 sgallen 在评论中所建议的那样,一种选择是为每个类别添加 .filter()。每个额外的 filter 都会添加更多的连接,这对于一小组类别来说应该不是问题。

aggregation approach。对于大量类别,此查询会更短,或许更快。

您还可以选择使用 custom queries

一些例子

测试设置:

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 )

是否有自定义查找的解决方案? docs.djangoproject.com/en/1.10/howto/custom-lookups 将“__in”切换为“__all”并让它创建正确的 sql 查询会很酷。
这个注释解决方案似乎是错误的。如果可能有三个标签(让我们为 t3 调用另外一个标签,并且一张照片具有标签 t2t3)怎么办。那么这张照片仍然会匹配给定的查询。
@beruic我认为这个想法是您将 num_tags=2 替换为 num_tags=len(tags);我希望硬编码的 2 只是为了举例。
@tbm 它仍然无法正常工作。 Photo.objects.filter(tags__in=tags) 匹配具有任何标签的照片,而不仅仅是那些具有所有标签的照片。其中一些仅具有所需标签之一的标签可能恰好具有您正在寻找的标签数量,而其中一些具有所有所需标签的标签可能还具有其他标签。
@beruic 注释只计算查询返回的标签,因此如果(查询返回的标签数量)==(搜索的标签数量)则包含该行; “额外”标签不会被搜索,因此不会被计算在内。我已经在我自己的应用程序中验证了这一点。
S
Sander van Leeuwen

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

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 具有一些更强大的功能,例如 overlapindex transforms


这是一个强大的解决方案,但是如果您在了解“列表中每个项目的 __in”用例之前创建了模型,那么您可能没有使用 ArrayField 并且可能需要进行一些深度重构......
d
demalexx

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

这是行不通的。您的查询示例不会为相关模型返回任何内容。
谢谢指正。我认为链接 filter 与在一个过滤器中对 Q 对象使用 and 相同......我的错误。
不用担心,我首先想到的也是 Q 对象。
如果您使用大型表和要比较的大型数据,这会使我们变慢。 (每人一百万)
如果您从 filter 切换到 exclude 并使用否定运算符,则此方法应该有效。像这样:res = Photo.exclude(~reduce(and_, [Q(tags__name=c) for c in categories]))
D
David

我使用了一个小函数,它在给定运算符的列表上迭代过滤器和列名:

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


A
Alexander Lekontsev

如果你像我一样在这个问题上苦苦挣扎,但没有提到对你有帮助,也许这个可以解决你的问题

而不是链接过滤器,在某些情况下,最好只存储前一个过滤器的 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


s
simon

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

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

f
fateme akrami
for category in categories:
    query = Photo.objects.filter(tags_name=category)

这段代码过滤您的照片,其中所有标签名称都来自类别。


t
tarasinf

如果我们想动态地做到这一点,按照这个例子:

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

一旦第二次迭代就无法工作,查询集将为空
J
Jeremy Caney
queryset = Photo.objects.filter(tags__name="vacaciones") | Photo.objects.filter(tags__name="verano")

这将返回带有“vacaciones”或“verano”标签的照片,而不是带有两个标签的照片......