我试图制作一个非常简单的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查询集 Select 所有Tag个字段,而Count只能在一个字段上计数.因此:需要Tag.objects.filter(post=OuterRef('pk')).only('pk')( Select 计数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相关问答推荐

在mySQL中使用子查询和WHERE子句的JOIN

如何有效地计算共同的朋友/追随者?

如何让Docker MySQL使用Unicode(utf-8)和初始化脚本?

在时间戳上搜索大的MySQL表很慢

MySQL-如何检测时间戳中的一天

Mysql-查找缺少列的表

查询具有JSON对象数组的列时,JSON_CONTAINS的MySQL语法错误

按唯一列排序,但保持匹配的列在一起

有没有比使用 MySQL 计划事件更好的方法来更新我的数据库表中的列?

使用另一个表中的值更新一个表中的值

为什么这个 NOT NULL 到 NULL 迁移会触发大量 I/O 操作?

是否可以在一个查询中将字符串附加到许多匹配的行

从 SQL 中的左连接和内连接中减go 计数

Mysql 相等性反对 false 或 true

在 Android 上运行 AMP (apache mysql php)

MySQL - 如何 Select 值在数组中的行?

是否可以在 macOS 上只安装 mysqldump

何时在 mysql 中使用 TEXT 而不是 VARCHAR

Python 是否支持 MySQL 准备好的语句?

在 MySQL Workbench 中导出超过 1000 条记录的查询结果