Một trong những vấn đề với ví dụ của bạn là bạn không thể sử dụng queryset.count()
như một truy vấn con, bởi vì .count()
cố gắng đánh giá bộ truy vấn và trả về số lượng.
Vì vậy, người ta có thể nghĩ rằng cách tiếp cận phù hợp sẽ là sử dụng Count()
thay thế. Có thể như thế này:
Post.objects.annotate(
count=Count(Tag.objects.filter(post=OuterRef('pk')))
)
Điều này sẽ không hoạt động vì hai lý do:
-
Thẻ
Tag
bộ truy vấn chọn tất cảTag
các trường, trong khiCount
chỉ có thể tính trên một lĩnh vực. Do đó:Tag.objects.filter(post=OuterRef('pk')).only('pk')
là cần thiết (để chọn đếm trêntag.pk
). -
Count
bản thân nó không phải làSubquery
lớp,Count
là mộtAggregate
. Vì vậy, biểu thức được tạo bởiCount
không được công nhận làSubquery
(OuterRef
yêu cầu truy vấn con), chúng tôi có thể khắc phục điều đó bằng cách sử dụngSubquery
.
Áp dụng các bản sửa lỗi cho 1) và 2) sẽ tạo ra:
Post.objects.annotate(
count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk')))
)
Tuy nhiên nếu bạn kiểm tra truy vấn đang được tạo:
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"
bạn sẽ thấy GROUP BY
mệnh đề. Điều này là do COUNT
là một hàm tổng hợp. Hiện tại nó không ảnh hưởng đến kết quả, nhưng trong một số trường hợp khác thì có thể. Đó là lý do tại sao tài liệu
đề xuất một cách tiếp cận khác, trong đó tập hợp được chuyển vào subquery
thông qua sự kết hợp cụ thể của các giá trị Tag
+ 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')
)
)
Cuối cùng điều này sẽ tạo ra:
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"