Uno de los problemas con su ejemplo es que no puede usar queryset.count()
como una subconsulta, porque .count()
intenta evaluar el conjunto de consultas y devolver el recuento.
Entonces uno puede pensar que el enfoque correcto sería usar Count()
en cambio. Tal vez algo como esto:
Post.objects.annotate(
count=Count(Tag.objects.filter(post=OuterRef('pk')))
)
Esto no funcionará por dos razones:
-
La
Tag
queryset selecciona todas lasTag
campos, mientras queCount
sólo puede contar con un campo. Así:Tag.objects.filter(post=OuterRef('pk')).only('pk')
es necesario (para seleccionar contar contag.pk
). -
Count
en sí mismo no es unaSubquery
clase,Count
es unAggregate
. Entonces la expresión generada porCount
no se reconoce como unaSubquery
(OuterRef
requiere subconsulta), podemos arreglar eso usandoSubquery
.
La aplicación de correcciones para 1) y 2) produciría:
Post.objects.annotate(
count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk')))
)
Sin embargo si inspecciona la consulta que se está produciendo:
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"
notará un GROUP BY
cláusula. Esto se debe a que COUNT
es una función agregada. Ahora mismo no afecta al resultado, pero en algunos otros casos puede que sí. Es por eso que los docs
sugiera un enfoque diferente, donde la agregación se mueva a la subquery
a través de una combinación específica de 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')
)
)
Finalmente esto producirá:
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"