[Django] 모델 필터링(Model Filtering)

2024. 10. 7. 16:55·Minding's Programming/Django
728x90
반응형

Django Shell에서 모델 필터링

모델 필터링은 해당 모델에서 내가 사용하고 싶은 레코드만 필터링해 조회하는 것을 말한다. Django Shell에서는 어떤 방법으로 모델 필터링을 할 수 있을까?

1. get() 메서드

>>> from polls.models import *

# id값으로 조회
>>> Question.objects.get(id=1)
<Question: 가장 추천하는 가을 캠핑장은 어디인가요?>

# 텍스트 시작값으로 조회
>>> Question.objects.get(question_text__startswith='야구')
<Question: 야구 vs 축구>

>>> q= Question.objects.get(question_text__startswith='야구')
>>> q.pub_date
datetime.datetime(2024, 10, 7, 5, 17, 5, 82114, tzinfo=datetime.timezone.utc)

# pub_date의 초 단위로 조회
>>> Question.objects.get(pub_date__second=5)
<Question: 야구 vs 축구>

위와 같이 get() 메서드를 이용하면 여러가지 원하는 조건 별로 필터링된 레코드를 얻을 수 있다. 하지만 get() 메서드는 아래와 같은 현상을 보이기도 한다.

 

>>> Question.objects.get(pub_date__year=2024)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/minding/anaconda3/envs/django_project/lib/python3.9/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/minding/anaconda3/envs/django_project/lib/python3.9/site-packages/django/db/models/query.py", line 640, in get
    raise self.model.MultipleObjectsReturned(
polls.models.Question.MultipleObjectsReturned: get() returned more than one Question -- it returned 3!

위 코드는 연도값이 2024인 항목을 조회한다. 하지만 해당 조건에 맞는 레코드가 여러 개일 경우 에러가 발생한다. 에러 코드를 읽어보니, get() 메서드는 해당 조건에 맞는 레코드 하나만 불러올 수 있다고 한다.

 

2. filter() 메서드

레코드를 여러개 불러오기 위해서는 filter() 메서드를 사용해주어야 한다.

>>> Question.objects.filter(pub_date__year=2024)
<QuerySet [<Question: 가장 추천하는 가을 캠핑장은 어디인가요?>, <Question: 야구 vs 
축구>, <Question: abc???>]>

# count()를 통해 레코드 개수를 알 수도 있다.
>>> Question.objects.filter(pub_date__year=2024).count()
3

 

기본적으로 여러 개를 반환할 경우, QuerySet 형태로 반환된다. (주의: 무조건 1개씩 연결되는 경우 Queryset 형태가 아님.) 따라서 .count() 등의 메서드를 이용해 개수를 셀 수도 있다.

 

QuerySet은 필터링을 거쳤을 때 사용된 SQL 쿼리문에 대해서도 조회할 수 있다.

# SQL 쿼리문 출력
>>> print(Question.objects.filter(pub_date__year=2024).query)
SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."pub_date" BETWEEN 2024-01-01 00:00:00 AND 2024-12-31 23:59:59.999999

# 문자열 필터링의 경우
>>> print( Question.objects.filter(question_text__startswith='야구').query)
SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."question_text" LIKE 야구% ESCAPE '\'

# question에 연결된 choice 조회할 경우
>>> q = Question.objects.get(pk=1)
>>> q.choice_set.all()
<QuerySet [<Choice: 포천>]>
>>> print(q.choice_set.all().query)
SELECT "polls_choice"."id", "polls_choice"."question_id", "polls_choice"."choice_text", "polls_choice"."votes" FROM "polls_choice" WHERE "polls_choice"."question_id" 
= 1

2024년 조건에 해당하는 레코드를 찾기 위해 WHERE절을 사용해 찾는다는 것을 알 수 있다. django에서는 이런 방식으로 SQL 쿼리문을 자동으로 작성해준다.

 

3. filter() 메서드를 활용해 여러가지 방법으로 필터링하기

위의 예시에서 필터링을 이용해 조회한 것 이외에도 여러가지 조건을 선택할 수 있다. 장고 공식 문서를 참고하면, 여러가지 조건을 걸 수 있다는 것을 알 수 있다. (공식문서 링크)

공식 문서에 여러가지 조건 항목 리스트가 있다.

 

이 중 대표적인 몇 가지는 아래와 같다.

  • contains - 특정 문자열이 포함된 레코드 조회
  • gt - 특정 값보다 큰 레코드 조회
  • regex - 정규표현식을 이용해 필터링
>>> from polls.models import *

# contains - 특정 문자열이 포함된 레코드 조회
>>> Question.objects.filter(question_text__contains='야구')

# gt - 해당 값보다 큰 것을 조회(초과)
Choice.objects.filter(votes__gt=0)

# regex - 정규표현식을 이용해 필터링
# '야구'가 들어가는 항목이 2개가 있음
>>> Question.objects.all()
<QuerySet [<Question: 가장 추천하는 가을 캠핑장은 어디인가요?>, <Question: 야구 vs 
축구>, <Question: abc???>, <Question: 야구를 왜 좋아하시나요?>]>

# '야구'가 처음 나오고 그 다음 '왜'가 나오는 문자열 찾기
>>> Question.objects.filter(question_text__regex=r'^야구.*왜')
<QuerySet [<Question: 야구를 왜 좋아하시나요?>]>

# 쿼리문 살펴보기
>>> print(Question.objects.filter(question_text__regex=r'^야구.*왜').query)
SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."question_text" REGEXP ^야구.*왜

 

4. 모델 관계 기반 필터링

모델 사이에 FK 등으로 관계가 연결되어 있을 경우 이를 활용한 필터링도 가능하다.

필터링을 위해 위와 같이 여러 개의 choice를 생성했다. 또한, 답변 앞에 질문이 어떤거였는지 __str__ 함수를 다음과 같이 조정했다.

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)


    def __str__(self):
        return f'[{self.question.question_text}] {self.choice_text}'

이제 Shell을 다시 시작해 question 값을 이용해 choice를 필터링해보자.

>>> from polls.models import *

# choice에 연결된 question의 text가 '가장'으로 시작하는 choice 레코드만 조회
>>> Choice.objects.filter(question__question_text__startswith='가장')

<QuerySet [<Choice: [가장 추천하는 가을 캠핑장은 어디인가요?] 포천>, <Choice: [가장
 추천하는 가을 캠핑장은 어디인가요?] 가평>, <Choice: [가장 추천하는 가을 캠핑장은  
어디인가요?] 강릉>]>

위 choice 레코드들 중 question의 text가 '가장'으로 시작할 때에만 레코드를 조회하도록 했다. 다른 레코드가 보이지 않는 것을 알 수 있다. 해당 쿼리를 살펴보면, INNER JOIN을 통해 해당 조건을 필터링하는 것을 알 수 있다.

>>> print(Choice.objects.filter(question__question_text__startswith='가장').query)
SELECT "polls_choice"."id", "polls_choice"."question_id", "polls_choice"."choice_text", "polls_choice"."votes" FROM "polls_choice" INNER JOIN "polls_question" ON ("polls_choice"."question_id" = "polls_question"."id") WHERE "polls_question"."question_text" LIKE 가장% ESCAPE '\'

 

반대로 .exclude() 메서드를 이용하면 해당 문자열을 제외한 모두를 조회하는 것도 가능하다.

>>> Question.objects.exclude(question_text__startswith='가장')
<QuerySet [<Question: 야구 vs 축구>, <Question: abc???>, <Question: 야구를 왜 좋아
하시나요?>]>

 

 

5. 필터링된 레코드들 수정 / 삭제하기

.update() - 필터링된 레코드 모두 수정

# 미리 '포천'에대한 votes값을 3으로 바꿔 놓음
>>> Choice.objects.filter(votes__gt=0)
<QuerySet [<Choice: 포천>]>

# update() 메서드를 이용해 필터링된 레코드의 votes 칼럼값 변경
>>> Choice.objects.filter(votes__gt=0).update(votes=0)
1

# 다시 조회
>>> Choice.objects.filter(votes__gt=0)
<QuerySet []>

 

.delete() - 필터링된 레코드 모두 수정

# 모두 조회
>>> Choice.objects.all()
<QuerySet [<Choice: 포천>, <Choice: c>, <Choice: 강릉>]>

# votes가 0 초과하는 레코드 조회
>>> Choice.objects.filter(votes__gt=0)
<QuerySet [<Choice: 강릉>]>

# votes가 0 초과하는 레코드 삭제 (1건 삭제됨)
>>> Choice.objects.filter(votes__gt=0).delete()
(1, {'polls.Choice': 1})

# 모두 조회
>>> Choice.objects.all()
<QuerySet [<Choice: 포천>, <Choice: c>]>

 

728x90

'Minding's Programming > Django' 카테고리의 다른 글

[Django] 폼(Forms)  (0) 2024.10.08
[Django] 뷰(views)와 템플릿(templates)  (0) 2024.10.08
[Django] 모델 메소드(Model Method)  (1) 2024.10.08
[Django] Django Shell 사용해보기  (0) 2024.10.07
[Django] Django 기본 설정  (0) 2024.10.07
'Minding's Programming/Django' 카테고리의 다른 글
  • [Django] 뷰(views)와 템플릿(templates)
  • [Django] 모델 메소드(Model Method)
  • [Django] Django Shell 사용해보기
  • [Django] Django 기본 설정
Minding
Minding
  • Minding
    Today's Minding
    Minding
  • 전체
    오늘
    어제
    • 울고넘는 딥러닝 (278)
      • Minding's Baseball (57)
        • MLB Statcast (29)
        • 머신러닝으로 홈런왕 예측하기 (3)
        • 야구칼럼 (12)
        • 야구 규칙, 용어 (1)
        • 2022-23 질롱 코리아 (8)
        • 류현진 등판경기 (4)
      • Minding's Programming (185)
        • 프로그래머스 코딩테스트 (21)
        • Knowledge (44)
        • Numpy & Pandas (6)
        • Excel (3)
        • Git (1)
        • Pygame (11)
        • CV (3)
        • Tensorflow tutorial (4)
        • Kaggle and Dacon (4)
        • 에러 코드 (8)
        • FastAPI (8)
        • Airflow (29)
        • Crawling (6)
        • Django (14)
        • AWS (18)
        • Spark (5)
      • Minding's Reading (30)
        • 머신러닝 딥러닝에 필요한 기초 수학 with 파이.. (2)
        • 칼만필터는 어렵지 않아 (11)
        • 밑바닥부터 시작하는 딥러닝 (6)
        • 메이저리그 야구 통계학 2e (8)
        • 논문읽기 (2)
        • 빅데이터를 지탱하는 기술 (1)
      • Minding's Life (5)
        • 주식 (4)
        • 각종 소식 (1)
  • 블로그 메뉴

    • 홈
    • Baseball
    • Programming
    • Reading
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    mlb stats api
    칼만필터는어렵지않아
    파이썬게임개발
    칼만필터
    코딩테스트
    Airflow
    칼만필터는어렵지않아python
    머신러닝
    게임개발
    KBO
    데이터분석
    데이터 엔지니어
    KalmanFilter
    에어플로우
    프로그래머스
    pygame
    메이저리그
    Python
    칼만필터는어렵지않아파이썬
    파이게임
    django
    질롱코리아
    파이썬
    야구
    AWS
    MLB
    django python
    딥러닝
    FastAPI
    넘파이
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Minding
[Django] 모델 필터링(Model Filtering)
상단으로

티스토리툴바