Един от проблемите с вашия пример е, че не можете да използвате queryset.count()
като подзаявка, защото .count()
се опитва да оцени набора от заявки и да върне броя.
Така че някой може да си помисли, че правилният подход би бил да се използва Count()
вместо. Може би нещо подобно:
Post.objects.annotate(
count=Count(Tag.objects.filter(post=OuterRef('pk')))
)
Това няма да работи по две причини:
-
Tag
queryset избира всичкиTag
полета, докатоCount
може да разчита само на едно поле. По този начин:Tag.objects.filter(post=OuterRef('pk')).only('pk')
е необходимо (за да изберете отчитане наtag.pk
). -
Count
сама по себе си не еSubquery
клас,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
е агрегатна функция. В момента това не влияе на резултата, но в някои други случаи може. Ето защо документите
предлагат различен подход, при който агрегирането се премества в 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"