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