Django Date(DateTime) QuerySet 관련 index 안 타는 것들 또는 유의해야 할 것

런던행·2021년 4월 7일
0

Django 업그레이드

목록 보기
15/17

장고 쿼리셋 도큐먼트에 날짜와 관련해서 여러가지 편리한 필드룩업(fieid lookup)을 가지고 있다. 무심코 사용하다가 실수 할수 있는 것들을 아느선에서 정리하고자 한다.

https://docs.djangoproject.com/en/3.2/ref/models/querysets/

range

import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))

pub_date가 Date 컬럼이면 sql query는 아래와 상응된다.

SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';

문제는 pub_date가 DateTime 타입이면 마지막 날짜에서 문제가 된다.

SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00';

마지막 날짜가 '2005-03-31 00:00:00' 되기에 3월 31일 포함 되지 않는다. 개발자의 의도는 31일까지 포함을 예상했지만 DateTime 타입에서는 포함되지 않을 수 있다.

Date타입에서 "__year", "__month", "__day" 사용 시 유의

qs = TeacherAttendance.objects.filter(
  center_id=16,
  date_attended__year=2020,
  date_attended__month=6,
  date_attended__day=20
)
SELECT .... FROM `erp_teacherattendance`
WHERE (`erp_teacherattendance`.`center_id` = 16 
AND `erp_teacherattendance`.`date_attended` BETWEEN 2020-01-01 AND 2020-12-31 
AND EXTRACT(MONTH FROM `erp_teacherattendance`.`date_attended`) = 6 
AND EXTRACT(DAY FROM `erp_teacherattendance`.`date_attended`) = 20)

필드 룩업 __year만 사용하면

BETWEEN 2020-01-01 AND 2020-12-31 

필드 룩업 __month or __day 사용하면

AND EXTRACT(MONTH FROM `erp_teacherattendance`.`date_attended`) = 6 
AND EXTRACT(DAY FROM `erp_teacherattendance`.`date_attended`) = 20)

EXTRACT로 치환되기에 인덱싱을 타지 않는다..
이거 이외에 EXTRACT로 치환되는 필드 룩업들이 있으니 잘 살펴 봐야할거 같다.

타임존 치환 관련

__date

>>> date_start
'2021-01-01 00:00'
>>> date_start_utc = datetime.strptime(date_start, '%Y-%m-%d %H:%M').astimezone(pytz.utc)
>>> album_qs = Album.objects.filter(center_id=16)
>>> a = album_qs.filter(created__date=date_start_utc)
>>> str(a.query)

created는 DateTime 타입이고, 타임존을 적용한 날짜객체(date_start_utc)로
__date 필터 사용 시, sql 쿼리문은 CONVERT_TZ() 사용하게 되어 인덱스를 타지 않는다.
타임존을 적용한 날짜 객체로 필터를 사용하게 되면 일반적으로 CONVERT_TZ() 하지 않는데 __date 필터를 사용하면 무조건 CONVERT_TZ() 치환 한다.

"SELECT ... WHERE (`albums_album`.`center_id` =
 16 AND DATE(CONVERT_TZ(`albums_album`.`created`, 'UTC', Asia/Seoul)) = 2021-01-01)"

결론

장고 queryset에서 제공하는 편리한 날짜 비교 룩업을 사용은 지양하고 수고로움이 있더라도 비교연산자를 사용하는게 더 좋아 보인다. (gt, gte, lt, lte...)

profile
unit test, tdd, bdd, laravel, django, android native, vuejs, react, embedded linux, typescript

0개의 댓글