Salah satu masalah dengan contoh Anda adalah Anda tidak dapat menggunakan queryset.count() sebagai subquery, karena .count() mencoba mengevaluasi kumpulan kueri dan mengembalikan hitungan.
Jadi orang mungkin berpikir bahwa pendekatan yang tepat adalah menggunakan Count() sebagai gantinya. Mungkin seperti ini:
Post.objects.annotate(
count=Count(Tag.objects.filter(post=OuterRef('pk')))
)
Ini tidak akan berhasil karena dua alasan:
-
Tagqueryset memilih semuaTagbidang, sementaraCounthanya bisa mengandalkan satu bidang. Jadi:Tag.objects.filter(post=OuterRef('pk')).only('pk')diperlukan (untuk memilih penghitungan padatag.pk). -
Countitu sendiri bukanSubquerykelas,CountadalahAggregate. Jadi ekspresi yang dihasilkan olehCounttidak dikenali sebagaiSubquery(OuterRefmembutuhkan subquery), kita dapat memperbaikinya dengan menggunakanSubquery.
Menerapkan perbaikan untuk 1) dan 2) akan menghasilkan:
Post.objects.annotate(
count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk')))
)
Namun jika Anda memeriksa kueri yang dihasilkan:
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"
Anda akan melihat GROUP BY ayat. Ini karena COUNT adalah fungsi agregat. Saat ini tidak mempengaruhi hasil, tetapi dalam beberapa kasus lain mungkin. Itulah mengapa dokumen
menyarankan pendekatan yang berbeda, di mana agregasi dipindahkan ke subquery melalui kombinasi spesifik 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')
)
)
Akhirnya ini akan menghasilkan:
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"