具有OuterRef的简单子查询

发布于 2021-01-29 18:17:14

我正在尝试使用一个非常简单的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.
关注者
0
被浏览
44
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    您的示例的问题之一是您不能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类,Count而是一个Aggregate。因此,由生成的表达式Count不能识别为SubqueryOuterRef需要子查询),我们可以使用来解决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"
    


知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看