2017-05-03 158 views
8

我试图做一个使用OuterRef的非常简单的子查询(不是为了实际目的,只是为了让它工作),而是一直运行到相同的错误。使用OuterRef的简单子查询

帖子/ 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. 

回答

17

一个与你的榜样的问题是,你不能使用queryset.count()作为一个子查询,因为.count()试图评估查询集并返回计数。

所以人们可能会认为正确的做法是使用Count()来代替。也许是这样的:

Post.objects.annotate(
    count=Count(Tag.objects.filter(post=OuterRef('pk'))) 
) 

这不会工作,原因有二:

  1. Tag查询集选择所有Tag领域,而Count只能在一个场数。因此:需要Tag.objects.filter(post=OuterRef('pk')).only('pk')(选择tag.pk的计数)。

  2. Count本身不是Subquery类,CountAggregate。所以Count生成的表达式不被识别为Subquery,我们可以通过使用Subquery来解决这个问题。

,正式版本将是:

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是一个Aggregate,现在它不会影响结果,但在某些情况下它可能会影响结果。这就是为什么docs建议一点点不同的方法,其中聚合是通过的values + annotate + values

Post.objects.annotate(
    count=Subquery(
     Tag.objects.filter(post=OuterRef('pk')) 
      .values('post') 
      .annotate(count=Count('pk')) 
      .values('count') 
    ) 
) 

的特定组合搬进subquery最后这将产生:

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" 
+0

谢谢,成功了!但是,当我将'pk__in = [1,2]'添加到标记过滤器时,我得到'django.core.exceptions.FieldError:表达式包含混合类型。你必须设置output_field'。 – mjuk

+1

您可以尝试打印'queryset.query'并直接在您的'RDBMS'中执行以查看您获得的回报。我猜测对于某些行,Count可能返回NULL而不是0.您可以尝试通过临时排除无数行的行来确认,即'.filter(count__gte = 1)'。然而,'Subquery'接受第二个参数,它是'output_field',你可以尝试将它设置为:'output_field = fields.IntegerField()' – Todor

+0

谢谢,这是我所需要的。 – mjuk