Ett av problemen med ditt exempel är att du inte kan använda queryset.count()
som en underfråga, eftersom .count()
försöker utvärdera frågeuppsättningen och returnera räkningen.
Så man kan tro att det rätta tillvägagångssättet skulle vara att använda Count()
istället. Kanske något sånt här:
Post.objects.annotate(
count=Count(Tag.objects.filter(post=OuterRef('pk')))
)
Detta kommer inte att fungera av två anledningar:
-
Tag
queryset väljer allaTag
fält, medanCount
kan bara räkna med ett fält. Således:Tag.objects.filter(post=OuterRef('pk')).only('pk')
behövs (för att välja att räkna påtag.pk
). -
Count
i sig är inte enSubquery
klass,Count
är ettAggregate
. Alltså uttrycket som genereras avCount
känns inte igen som enSubquery
(OuterRef
kräver underfråga), kan vi fixa det genom att användaSubquery
.
Att tillämpa korrigeringar för 1) och 2) skulle producera:
Post.objects.annotate(
count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk')))
)
Men om du inspekterar frågan som produceras:
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"
du kommer att märka en GROUP BY
klausul. Detta beror på att COUNT
är en aggregerad funktion. Just nu påverkar det inte resultatet, men i vissa andra fall kan det. Det är därför som dokumenten
föreslå ett annat tillvägagångssätt, där aggregeringen flyttas till subquery
via en specifik kombination av 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')
)
)
Slutligen kommer detta att producera:
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"