具有OuterRef的简单子查询
我正在尝试使用一个非常简单的Subquery
方法OuterRef
(不是出于实际目的,而是为了使其正常工作),但是我一直遇到相同的错误。
posts/models.py
码
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=120)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=120)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
manage.py shell
码
>>> from django.db.models import OuterRef, Subquery
>>> from posts.models import Tag, Post
>>> tag1 = Tag.objects.create(name='tag1')
>>> post1 = Post.objects.create(title='post1')
>>> post1.tags.add(tag1)
>>> Tag.objects.filter(post=post1.pk)
<QuerySet [<Tag: tag1>]>
>>> tags_list = Tag.objects.filter(post=OuterRef('pk'))
>>> Post.objects.annotate(count=Subquery(tags_list.count()))
最后两行应为我提供每个Post对象的标签数量。在这里,我不断收到相同的错误:
ValueError: This queryset contains a reference to an outer query and may only be used in a subquery.
-
您的示例的问题之一是您不能
queryset.count()
用作子查询,因为它.count()
试图评估查询集并返回计数。因此,人们可能会认为正确的方法是使用
Count()
替代方法。也许是这样的:Post.objects.annotate( count=Count(Tag.objects.filter(post=OuterRef('pk'))) )
这不能工作有两个原因:
-
查询集
Tag
选择所有Tag
字段,而Count
只能依靠一个字段。因此:Tag.objects.filter(post=OuterRef('pk')).only('pk')
是必需的(选择依靠tag.pk
)。 -
Count
本身不是一个Subquery
类,Count
而是一个Aggregate
。因此,由生成的表达式Count
不能识别为Subquery
(OuterRef
需要子查询),我们可以使用来解决Subquery
。
应用1)和2)的修复程序将产生:
Post.objects.annotate( count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk'))) )
但是, 如果您检查正在生成的查询:
SELECT "tests_post"."id", "tests_post"."title", COUNT((SELECT U0."id" FROM "tests_tag" U0 INNER JOIN "tests_post_tags" U1 ON (U0."id" = U1."tag_id") WHERE U1."post_id" = ("tests_post"."id")) ) AS "count" FROM "tests_post" GROUP BY "tests_post"."id", "tests_post"."title"
您会注意到一个
GROUP BY
条款。这是因为COUNT
是聚合函数。现在它不会影响结果,但是在其他情况下可能会影响结果。这就是为什么文档提出了一种不同的方法的原因,该方法是subquery
通过特定的values
+
annotate
+组合将聚合移动到中values
:Post.objects.annotate( count=Subquery( Tag.objects .filter(post=OuterRef('pk')) # The first .values call defines our GROUP BY clause # Its important to have a filtration on every field defined here # Otherwise you will have more than one group per row!!! # This will lead to subqueries to return more than one row! # But they are not allowed to do that! # In our example we group only by post # and we filter by post via OuterRef .values('post') # Here we say: count how many rows we have per group .annotate(count=Count('pk')) # Here we say: return only the count .values('count') ) )
最终将产生:
SELECT "tests_post"."id", "tests_post"."title", (SELECT COUNT(U0."id") AS "count" FROM "tests_tag" U0 INNER JOIN "tests_post_tags" U1 ON (U0."id" = U1."tag_id") WHERE U1."post_id" = ("tests_post"."id") GROUP BY U1."post_id" ) AS "count" FROM "tests_post"
-