Published on

Django ORM에서 Q와 Count_filter로 조건부 집계 및 필터링하기

Authors

Django ORM을 사용할 때 다음과 같은 요구사항을 자주 마주하게 됩니다.

  • "관련된 객체 중 특정 조건을 제외한 개수만 세고 싶다."

  • "데이터가 없는 경우는 쿼리 결과에서 제외하고 싶다."

이번 글에서는 Django ORM의 ~Q(부정 조건)와 Count( 구체적으로는 filter=...)를 사용하여 위 요구사항을 해결하는 방법을 기록합니다.

1. ~Q의 이해

Django의 Q 객체는 복합적인 조건을 처리할 때 사용하는데, ~Q는 특정 조건을 부정(NOT)할 때 사용합니다.

예를 들어서 이름이 '일론'이 아닌 유저를 찾는 경우

from django.db.models import Q

User.objects.filter(~Q(name='일론'))

SQL로는 다음과 같습니다.

SELECT * FROM user WHERE name != '일론';

기본적으로 Q의 기능으로 여러 조건을 조합하여 사용할 수도 있습니다.

# 이름이 '일론'이 아니거나 나이가 30 이상인 유저
User.objects.filter(~Q(name='일론') | Q(age__gte=30))

2. Count(filter=...)로 조건부 집계하기

Django의 Count() 함수는 특정 조건을 만족하는 객체만 집계할 수 있도록 filter 인자를 제공합니다. 이를 알면 조금더 창의적으로 ORM을 사용할 수 있습니다.

from django.db.models import Count, Q

# 'inactive' 상태가 아닌 related_items 개수 세기
SomeModel.objects.annotate(
    active_item_count=Count(
        'related_items',
        filter=~Q(related_items__status='inactive')
    )
)

2.1 Count에서는 exclude() 사용 불가

Count() 함수 내에서는 직접 .exclude() 메서드를 사용할 수 없습니다.

그래서 대신에 filter=~Q(...) 형식으로 조건을 부정(not)하여 같은 효과를 낼 수 있습니다.

예를 들어, 아래와 같은 사용은 불가능합니다.

# 잘못된 사용 예
Count('related_items').exclude(status='inactive')  # 사용 불가능

3. 실제 예시

예를들어, 댓글이 없는 게시글은 결과에서 제외하려고 합니다. 블로그 글(Post)이 여러 댓글(Comment)을 가지고 있으며, 댓글 중 상태(status)가 'DELETED'가 아닌 댓글의 개수만 세고 싶다고 가정해 보겠습니다.

Post.objects.annotate(
    valid_comments_count=Count(
        'comments',
        filter=~Q(comments__status='DELETED'),
        distinct=True
    )
).filter(
    valid_comments_count__gt=0
)

이렇게 되면 상태가 'DELETED'가 아닌 댓글만 개수를 셉니다. 즉 댓글 개수가 0보다 큰 게시글만 조회합니다.

Django ORM을 사용할 때 ~Q와 Count(filter=...)를 활용하면, 복잡한 조건부 집계와 필터링을 조금 창의적인 방식으로 깔끔하게 처리할 수 있습니다.

  • hongreat 블로그의 글을 봐주셔서 감사합니다!^^
  • 내용에 잘못된 부분이나 의문점이 있으시다면 댓글 부탁 & 환영 합니다~!
  • (하단의 버튼을 누르시면 댓글을 보거나 작성할 수 있습니다.)
Buy Me A Coffee