我有一个标有“一”和“二”的 Result 对象。当我尝试查询标记为“一”和“二”的对象时,我什么也得不到:
q = Result.objects.filter(Q(tags__name="one") & Q(tags__name="two"))
print len(q)
# prints zero, was expecting 1
为什么它不适用于 Q?我怎样才能让它工作?
我有一个标有“一”和“二”的 Result 对象。当我尝试查询标记为“一”和“二”的对象时,我什么也得不到:
q = Result.objects.filter(Q(tags__name="one") & Q(tags__name="two"))
print len(q)
# prints zero, was expecting 1
为什么它不适用于 Q?我怎样才能让它工作?
django-taggit 实现标记的方式本质上是通过多对多关系。在这种情况下,数据库中有一个单独的表来保存这些关系。它通常被称为“直通”或中间模型,因为它连接了两个模型。在 django-taggit 的情况下,这称为TaggedItem
. 所以你有一个Result
模型,它是你的模型,你有两个模型Tag
,TaggedItem
由 django-taggit 提供。
当您进行查询时,例如Result.objects.filter(Q(tags__name="one"))
它转换为在 Result 表中查找在 TaggedItem 表中具有相应行的行,在 Tag 表中具有 name="one" 的相应行。
尝试匹配两个标记名称将转换为在 Result 表中查找在 TaggedItem 表中具有相应行的行,在 Tag 表中具有 name="one" AND name="two" 的相应行. 您显然永远不会拥有它,因为您连续只有一个值,它要么是“一”,要么是“二”。
这些细节在 django-taggit 实现中是隐藏的,但是当对象之间存在多对多关系时,就会发生这种情况。
要解决此问题,您可以:
每次评估结果的标签后查询标签,正如其他人的答案中所建议的那样。这对于两个标签可能没问题,但当您需要查找设置了 10 个标签的对象时就不好了。这是执行此操作的一种方法,它会导致两个查询并为您提供结果:
# get the IDs of the Result objects tagged with "one"
query_1 = Result.objects.filter(tags__name="one").values('id')
# use this in a second query to filter the ID and look for the second tag.
results = Result.objects.filter(pk__in=query_1, tags__name="two")
您可以通过单个查询来实现此目的,因此您只需从应用程序到数据库的一次行程,如下所示:
# create django subquery - this is not evaluated, but used to construct the final query
subquery = Result.objects.filter(pk=OuterRef('pk'), tags__name="one").values('id')
# perform a combined query using a subquery against the database
results = Result.objects.filter(Exists(subquery), tags__name="two")
这只会访问数据库一次。(注意:过滤子查询需要 django 3.0)。
但是您仍然限于两个标签。如果你需要检查 10 个或更多标签,上面是不是真的可行......
而是直接查询关系表,并以一种为您提供对象 ID 的方式聚合结果。
# django-taggit uses Content Types so we need to pick up the content type from cache
result_content_type = ContentType.objects.get_for_model(Result)
tag_names = ["one", "two"]
tagged_results = (
TaggedItem.objects.filter(tag__name__in=tag_names, content_type=result_content_type)
.values('object_id')
.annotate(occurence=Count('object_id'))
.filter(occurence=len(tag_names))
.values_list('object_id', flat=True)
)
TaggedItem 是 django-taggit 实现中包含关系的隐藏表。上面将查询该表并聚合所有引用“一”或“二”标签的行,按对象的 ID 对结果进行分组,然后选择对象 ID 具有您正在查找的标签数量的那些为了。
这是一个单一的查询,最后会为您提供所有已使用两个标签标记的对象的 ID。无论您需要 2 个标签还是 200 个标签,这也是完全相同的查询。
请查看此内容并让我知道是否有任何需要澄清的地方。
首先,这三个是相同的:
Result.objects.filter(tags__name="one", tags__name="two")
Result.objects.filter(Q(tags__name="one") & Q(tags__name="two"))
Result.objects.filter(tags__name_in=["one"]).filter(tags__name_in=["two"])
我认为名称字段是 CharField,没有记录可以同时等于“一”和“二”。
在 python 代码中,查询看起来像这样(总是错误的,为什么你没有得到结果):
from random import choice
name = choice(["abtin", "shino"])
if name == "abtin" and name == "shino":
我们使用 Q 对象来实现 OR 或复杂的查询
q = Result.objects.filter(tags_name _in =["one"]).filter(tags_name _in =["two"])
如果期望多个唯一对象,请添加 .distinct() 以删除重复项
在有效的示例中,您将结束两个 python 对象(查询集)。这适用于任何记录,不一定适用于具有one
ANDtwo
作为标签的同一记录。
ps:为什么要使用in
过滤器?