我试图制作一个非常简单的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()试图计算queryset并返回计数.

因此,人们可能会认为正确的方法是使用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(OuterRef需要子查询),我们可以使用Subquery来修复这个问题.

对1)和2)应用修复将产生:

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

However

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是一个聚合函数.目前,它并不影响结果,但在其他一些情况下,它可能会影响结果.这就是为什么docs建议采用不同的方法,通过values+annotate+values的特定组合将聚合移动到subquery:

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"

Mysql相关问答推荐

用两个新列制作表格,按偶数或奇数对另一列进行分类,并将一个类别中的所有偶数和奇数相加

SUBSTRING_INDEX 获取第 n 个值

如何查看 rdsadmin 在 mysql 实例上的权限?

添加一个日期字段大于另一个日期字段的要求

显示值为空的所有列名

估计行数 SQL

判断一对记录是否属于多个组ID

使用带有 ELSEIF 和 ELSE 的 3 列更新问题

#1030 - 从存储引擎 Aria 收到错误 176“读取错误校验和的页面”

如何使用axios发布查询参数?

.NET Core 2.1 Identity 获取所有用户及其相关角色

如何在 MySQL 数据库中存储表情符号字符

表 'DBNAME.hibernate_sequence' 不存在

导入 data.sql MySQL Docker 容器

Amazon RDS Aurora 与 RDS MySQL 与 EC2 上的 MySQL?

MySql.Data.MySqlClient.MySqlException:“主机 localhost 不支持 SSL 连接.”

不支持扫描,将 driver.Value 类型 []uint8 存储到 *time.Time 类型中

Yii2 如何进行 where AND 或 OR 条件分组?

Laravel 5.1 - 判断数据库连接

的表存储引擎在按查询排序时没有此选项(错误 1031)