Django annotate 로 Count 를 했을 때 비정상적으로 나오는 경우
Django ORM 을 사용할 때 여러개의 count 값을 사용해야하는 경우, 값이 정상적으로 반환되지 않는 상황에 대해 알아보자.
Nov 21, 2023
Django ORM 을 사용할 때 여러개의 count 값을 사용해야하는 경우, 값이 정상적으로 반환되지 않는 상황에 대해 알아보자.
내가 겪은 상황을 먼저 말해보자면, 특정 그룹을 조회할때 그룹에 참여한 멤버의 수(member_count)와, 그룹이 좋아요를 받은 수(like_count)를 각 각 반환해야했다.
Group.objects.annotate(like_count=Count("group_like"), member_count=Count("group_member"))
# 기대되는 값
like_count -> 1
member_count -> 4
실제 코드와는 차이가 있지만 큰 틀에서 보면 위의 코드와 동일하다.
그리고 반환되는 데이터는 아래와 같이 기대되는 값가는 다르게 4로 동일하게 나타나는 잘못된 경우가 발생한다.
group = Group.objects.get(id=123)
group.like_count => 4
group.member_count => 4
이 이슈의 원인은 annotate 로 여러개의 count 를 aggreagation 할 때 서브쿼리 대신에 조인을 사용하면서 잘못된 결과를 반환하게 된다(라고 문서에 나와있다: 문서 바로가기). 이렇게 aggregate 를 할 때 이 문제점은 피할 수 없지만 Count 에 distinct 옵션을 추가하면서 이런 문제를 해결할 수 있다.
Group.objects.annotate(
like_count=Count("group_like", distinct=True),
member_count=Count("group_member", distinct=True)
)
SQL 쿼리를 확인해보면 아래처럼 Distinct 옵션이 추가가 되는 것을 볼 수 있다.
# distinct X
SELECT "group"."id",
"group"."is_deleted",
COUNT("group_like"."id") AS "like_count",
COUNT("group_member"."id") AS "member_count"
FROM "group"
LEFT OUTER JOIN "group_like"
ON ("group"."id" = "group_like"."group_id")
LEFT OUTER JOIN "group_member"
ON ("group"."id" = "group_member"."group_id")
WHERE NOT "group"."is_deleted"
GROUP BY "group"."id"
LIMIT 21
# distinct O
SELECT "group"."id",
"group"."is_deleted",
COUNT(DISTINCT "group_like"."id") AS "like_count",
COUNT(DISTINCT "group_member"."id") AS "member_count"
FROM "group"
LEFT OUTER JOIN "group_like"
ON ("group"."id" = "group_like"."group_id")
LEFT OUTER JOIN "group_member"
ON ("group"."id" = "group_member"."group_id")
WHERE NOT "group"."is_deleted"
GROUP BY "group"."id"
LIMIT 21
아무튼 첫번째 쿼리에서 비정상적으로 데이터를 가져오는 이유는, 각각의 집계에 대해 조인을 수행하게 되는데 이때 중복된 레코드가 생성된다고 한다. 그러다보니 쿼리를 호출해서 가져온올 때 결과가 중복되어서 생긴다고 한다. 따라서 Distinct 를 사용하게 되면 중복된 레코드를 제거하고 고유값만 사용하게 함으로써 비정상적인 데이터가 아닌 올바른 값을 사용할 수 있게 된다.
Share article