Jednym z problemów z twoim przykładem jest to, że nie możesz użyć queryset.count() jako podzapytanie, ponieważ .count() próbuje ocenić zestaw zapytań i zwrócić liczbę.
Można więc pomyśleć, że właściwym podejściem byłoby użycie Count() zamiast. Może coś takiego:
Post.objects.annotate(
count=Count(Tag.objects.filter(post=OuterRef('pk')))
)
To nie zadziała z dwóch powodów:
-
Tagqueryset zaznacza wszystkieTagpola, podczas gdyCountmoże liczyć tylko na jedno pole. Tak więc:Tag.objects.filter(post=OuterRef('pk')).only('pk')jest potrzebne (aby wybrać liczenie natag.pk). -
Countsamo w sobie nie jestSubqueryklasa,CountjestAggregate. Zatem wyrażenie wygenerowane przezCountnie jest rozpoznawany jakoSubquery(OuterRefwymaga podzapytania), możemy to naprawić za pomocąSubquery.
Zastosowanie poprawek dla 1) i 2) spowodowałoby:
Post.objects.annotate(
count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk')))
)
Jednak jeśli sprawdzisz tworzone zapytanie:
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"
zauważysz GROUP BY klauzula. Dzieje się tak, ponieważ COUNT jest funkcją agregującą. W tej chwili nie ma to wpływu na wynik, ale w niektórych innych przypadkach może. Dlatego dokumenty
zaproponuj inne podejście, w którym agregacja jest przenoszona do subquery za pomocą określonej kombinacji 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')
)
)
W końcu spowoduje to:
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"