使用 OuterRef 的简单子查询
Simple Subquery with OuterRef
我正在尝试制作一个使用 OuterRef
的非常简单的 Subquery
(不是出于实用目的,只是为了让它工作),但我将 运行 保留在相同的位置错误。
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()
尝试评估查询集并 return 计数。
因此,人们可能会认为正确的方法是使用 Count()
。也许是这样的:
Post.objects.annotate(
count=Count(Tag.objects.filter(post=OuterRef('pk')))
)
这行不通有两个原因:
Tag
查询集select全是Tag
字段,而Count
只能算一个字段。因此:Tag.objects.filter(post=OuterRef('pk')).only('pk')
是必需的(select 指望 tag.pk
)。
Count
本身不是Subquery
class,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
是一个聚合函数。现在它不会影响结果,但在其他一些情况下可能会。这就是为什么 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"
django-sql-utils 包使这种子查询聚合变得简单。只需 pip install django-sql-utils
然后:
from sql_util.utils import SubqueryCount
posts = Post.objects.annotate(
tag_count=SubqueryCount('tag'))
SubqueryCount 的 API 与 Count 相同,但它在 SQL 中生成子查询而不是加入相关的 table.
我正在尝试制作一个使用 OuterRef
的非常简单的 Subquery
(不是出于实用目的,只是为了让它工作),但我将 运行 保留在相同的位置错误。
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()
尝试评估查询集并 return 计数。
因此,人们可能会认为正确的方法是使用 Count()
。也许是这样的:
Post.objects.annotate(
count=Count(Tag.objects.filter(post=OuterRef('pk')))
)
这行不通有两个原因:
Tag
查询集select全是Tag
字段,而Count
只能算一个字段。因此:Tag.objects.filter(post=OuterRef('pk')).only('pk')
是必需的(select 指望tag.pk
)。
来解决这个问题Count
本身不是Subquery
class,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
是一个聚合函数。现在它不会影响结果,但在其他一些情况下可能会。这就是为什么 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"
django-sql-utils 包使这种子查询聚合变得简单。只需 pip install django-sql-utils
然后:
from sql_util.utils import SubqueryCount
posts = Post.objects.annotate(
tag_count=SubqueryCount('tag'))
SubqueryCount 的 API 与 Count 相同,但它在 SQL 中生成子查询而不是加入相关的 table.