터칭 데이터
장고(Django) 모델 필터링(Model Filtering) 본문
모델 필터링(Model Filtering)이란
모든 데이터를 일괄적으로 가져온 뒤 내가 필요한 데이터를 찾으면 시간도 오래 걸리고 불편하겠죠
장고 모델에서는 내가 정한 조건에 부합하는 데이터만 가져오도록 하는 방법들을 제공합니다.
이를 모델 필터링이라고 합니다.
get 방식
>>> from polls.models import *
>>> Question.objects.get(id=1)
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>
>>> Question.objects.get(question_text__startswith='휴가를')
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>
>>> q = Question.objects.get(question_text__startswith='휴가를')
>>> q.pub_date
datetime.datetime(2023, 10, 31, 14, 19, 40, tzinfo=datetime.timezone.utc)
>>> Question.objects.get(pub_date__second=40)
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>
get()안에 조건을 주고 거기에 일치하는 데이터만 필터링해 가져오는 방법입니다.
id=1
id가 1인 데이터를 가져와
question_text__startswith='휴가를'
question_text가 '휴가를'로 시작하는 데이터를 가져와
( __startswith 앞에 언더바가 2개 붙음에 주의하세요)
pub_date__second=40, pub
질문 입력을 40초에 했던 데이터를 가져와
(역시 언더바 2개 입니다.)
그런데!
>>> Question.objects.get(pub_date__year=2023)
Traceback (most recent call last):
File "<console>", line 1, in <module>
# ~(중략)~
polls.models.Question.MultipleObjectsReturned:
get() returned more than one Question -- it returned 4!
데이터 입력 연도가 2023이라는 명령어를 get으로 주자
에러가 납니다.
자세히 읽어 보면 get()은 하나의 결과만 반환하는데 2023년이 입력 연도인 데이터들은 4개라서
에러가 일어났다는 뜻입니다.
get()은 결과를 하나만 반환합니다.
하지만 모델 필터링을 이용하면 여러개의 결과를 반환 받을 수 있습니다.
filter() 방식
그러면 여러개의 결과도 반환받을 수 있도록 get이 아닌 filter 메서드를 사용해봅니다.
>>> Question.objects.filter(pub_date__year=2023)
<QuerySet [
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>,
<Question: 제목: 가장 좋아하는 디저트는?, 날짜: 2023-10-31 14:22:37+00:00>,
<Question: 제목: 커피 vs 녹차, 날짜: 2023-11-01 05:37:56.580508+00:00>,
<Question: 제목: abc???, 날짜: 2023-11-01 05:51:02.905096+00:00>
]>
질문 입력 연도가 2023년인 데이터들이 4개 있지만 get 방식과 달리 무사히 결과들이 모두 출력되었습니다.
QuerySet 반환
쿼리셋은 조건에 부합하는 모델의 데이터(객체) 목록입니다.
>>> Question.objects.filter(pub_date__year=2023).count()
4
>>> Question.objects.all()
<QuerySet [<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>, <Question: 제목: 가장 좋아하는 디저트는?, 날짜: 2023-10-31 14:22:37+00:00>, <Question: 제목: 커피 vs 녹차, 날짜: 2023-11-01 05:37:56.580508+00:00>, <Question: 제목: abc???, 날짜: 2023-11-01 05:51:02.905096+00:00>]>
>>> Question.objects.all().count()
4
>>> q = Question.objects.all().first()
>>> q.choice_set.all()
<QuerySet [<Choice: 바다>, <Choice: 강>]>
>>> q.choice_set.all().count()
2
>>> q.choice_set.all()
<QuerySet [<Choice: 바다>, <Choice: 강>]>
쿼리셋은 결과 여러개일 수 있는 경우로 .count()로 데이터의 갯수를 확인할 수 있습니다.
q는 Question 모델의 객체로 q를 참조하는 Choice의 데이터가 여러개일 수 있기 때문에
q.choice_set.all().count()가 쿼리셋으로 정상작동 하지만
>>> choice = q.choice_set.first()
>>> choice
<Choice: 바다>
>>> choice.question
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>
>>> choice.question.count()
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Question' object has no attribute 'count'
Choice 객체 choice의 경우는 참조하는 Question 모델의 데이터는 반드시 하나이기 때문에
choice.question사용시 바로 Question 객체가 나오며 쿼리셋이 아닙니다.
따라서 count()를 choice.question은 객체이지 쿼리셋이 아니기 때문에 사용하면 해당 기능을 지원하지 않는다며 에러가 뜹니다.
결론
Question에서 Choice로 갈때는 여러개일 수 있으므로 쿼리셋
Choice에서 Question은 반드시 하나이므로 쿼리셋이 아닌 객체
쿼리셋의 SQL 쿼리 살펴보기
쿼리셋에서는 실제 데이터를 가져오는 SQL 쿼리문을 확인할 수 있습니다.
filter() 메서드 뒤에 .query를 붙이면
>>> Question.objects.filter(pub_date__year=2023).query
<django.db.models.sql.query.Query object at 0x0000022476397190> # print()없이는 객체
>>> print(Question.objects.filter(pub_date__year=2023).query) # print()로 객체를 쿼리문으로 확인
SELECT
"polls_question"."id",
"polls_question"."question_text",
"polls_question"."pub_date"
FROM
"polls_question"
WHERE
"polls_question"."pub_date"
BETWEEN 2023-01-01 00:00:00 AND 2023-12-31 23:59:59.999999
>>> print(Question.objects.filter(pub_date__second=40).query) # 연도 대신 초로 필터링
SELECT
"polls_question"."id",
"polls_question"."question_text",
"polls_question"."pub_date"
FROM
"polls_question"
WHERE
django_datetime_extract(second, "polls_question"."pub_date", UTC, UTC) = 40
실제 DB에서 사용되는 SQL 쿼리문을 확인할 수 있습니다.
다른 예시도 살펴보면
>>> print(Question.objects.all().query)
SELECT
"polls_question"."id",
"polls_question"."question_text",
"polls_question"."pub_date"
FROM
"polls_question"
>>> 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 '\'
all()의 쿼리는 별도의 WHERE절 없이 모든 것을 불러오고
__startswith 조건은 SQL의 WHERE LIKE가 사용됨을 볼 수 있습니다.
그런데 쿼리문을 읽을 때 추가적으로 알아두면 좋은 점이 있습니다.
>>> q = Question.objects.get(pk=1)
>>> q.choice_set.all()
<QuerySet [<Choice: 바다>, <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
q에 기본키(여기서는 id)가 1인 Question 객체를 담았습니다.
기본키가 1인 Question 객체를 참조하는 Choice들은 바다, 강 이렇게 2개라고 쿼리셋이 출력됩니다.
이때 쿼리를 확인해보면
장고 쉘 상에서는 우리가 비록 q.~ 로 시작, 즉 Question의 객체로 데이터 탐색을 시작한 것 같지만
사실은 Question의 choice_set에 대해서 데이터를 가져오는 것이기 때문에
q.choice_set.all()를 분해해보면
q는 WHERE 절에서
choice_set은 FROM에 관여한다는 점을 알 수 있습니다.
이렇게 쿼리문을 확인하는 작업은 필터가 복잡하게 구성된 경우 SQL 쿼리문이 잘 적혀 실행되는지 확인할 때 유용합니다.
'장고 (Django)' 카테고리의 다른 글
장고(Django) 모델 관계기반 필터링 (0) | 2023.11.02 |
---|---|
장고(Django) 모델 필터링(Model Filtering) 2 (0) | 2023.11.01 |
장고(Django) 쉘에서 데이터 수정하고 삭제하기 (0) | 2023.11.01 |
장고(Django) 쉘에서 데이터 입력하기 (0) | 2023.11.01 |
장고(Django) 현재 시간 구하기 (0) | 2023.11.01 |