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:
-
Tag
queryset memilih semuaTag
bidang, sementaraCount
hanya bisa mengandalkan satu bidang. Jadi:Tag.objects.filter(post=OuterRef('pk')).only('pk')
diperlukan (untuk memilih penghitungan padatag.pk
). -
Count
itu sendiri bukanSubquery
kelas,Count
adalahAggregate
. Jadi ekspresi yang dihasilkan olehCount
tidak dikenali sebagaiSubquery
(OuterRef
membutuhkan 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"