프로젝트를 진행하면서 단일 날짜(start_date == end_date)로 시뮬레이션 데이터를 조회할 때,
해당 날짜의 레코드가 전혀 조회되지 않는 문제가 있었다.
처음엔 쿼리 조건이 단순해서 금방 해결될 줄 알았는데, 생각보다 꽤 깊은 이슈였다.
---
문제 상황
start_date 와 end_date 를 동일하게 입력했을 때 하루 전체 레코드가 조회되지 않았다.
query = query.where(Simulation.created_at >= start_date)
query = query.where(Simulation.created_at <= end_date)
즉, created_at이 datetime 컬럼일 경우 00:00:00 시점에 정확히 일치하는 데이터만 포함되고,
그 날짜의 나머지 레코드는 모두 누락되었다.
원인 분석
이 문제는 단순히 비교 연산자(<=) 문제처럼 보였지만, 실제로는 여러 요인이 복합적으로 작용했다.
- 범위 누락
- start_date <= created_at <= end_date는 시각이 정확히 00:00:00인 데이터만 포함한다.
- 즉, 하루 전체(00:00:00 ~ 23:59:59)를 포함하지 못한다.
- 타입 불일치
- 입력된 start_date / end_date는 date 타입
- DB의 created_at은 datetime 타입
- SQLAlchemy 비교 시 타입이 달라 미묘한 오차가 발생할 수 있다.
- 시간대(Timezone) 차이
- DB는 UTC로 저장, 애플리케이션은 KST로 처리하는 경우
- 단순한 날짜 비교에서도 실제 범위가 어긋나서 데이터 누락이 발생한다.
해결 방법
하루 전체 포함
날짜가 같을 때 하루 전체 데이터를 포함시키려면,
<= end_date 대신 < end_date + 1일 조건을 사용하는 것이 가장 단순하고 명확하다.
from datetime import timedelta
if start_date:
query = query.where(Simulation.created_at >= start_date)
if end_date:
query = query.where(Simulation.created_at < end_date + timedelta(days=1))
이 방식은 start_date == end_date인 경우에도
해당 날짜의 00:00:00부터 23:59:59까지 모든 레코드를 포함한다.
타입 안전 처리 (선택 사항)
DB 필드가 datetime이라면 date를 datetime으로 변환해 비교하는 게 더 안전하다.
from datetime import datetime, timedelta
start_dt = datetime.combine(start_date, datetime.min.time())
end_dt = datetime.combine(end_date + timedelta(days=1), datetime.min.time())
query = query.where(Simulation.created_at >= start_dt)
query = query.where(Simulation.created_at < end_dt)
시간대 고려
시간대가 다르면 비교 자체가 어긋난다.
- DB와 애플리케이션의 타임존을 일치시키거나
- timezone-aware datetime을 사용해 UTC 변환 후 비교하는 방식이 안전하다.
디버깅 체크포인트
문제를 해결하는 과정에서 아래와 같은 점검 단계를 거쳤다.
- SQLAlchemy 쿼리를 실제 SQL 문자열로 출력해 확인
- print(str(query))
- start_date / end_date 값과 DB created_at 실제 저장 값을 직접 비교
- 단일 날짜 조회 시 < start_date + 1 day 조건이 올바르게 작동하는지 검증
- DB와 애플리케이션 간 시간대 차이 확인
구현하면서 배운 점
- date와 datetime의 차이를 명확히 이해해야 한다.
단순 비교라도 타입이 다르면 의도치 않은 결과가 나온다. - 단일 날짜 조회도 결국 "범위 조회"라는 사실을 깨달았다.
start_date == end_date라고 해도 하루 전체를 포함하는 로직이 필요하다. - 공통 패턴은 헬퍼 함수로 추상화하면 유지보수가 훨씬 쉬워진다.
- 시간대(Timezone)는 평소엔 잘 안 보이지만, 실서비스에선 반드시 고려해야 하는 요소다.
- SQLAlchemy 쿼리를 실제 SQL로 출력해서 확인하는 습관은 디버깅에 매우 유용하다.
최종 권장 패턴
최종적으로 날짜 필터를 별도 헬퍼 함수로 정리했다.
def apply_date_filter(query, start_date, end_date, model_field):
from datetime import timedelta
if start_date:
query = query.where(model_field >= start_date)
if end_date:
query = query.where(model_field < end_date + timedelta(days=1))
return query
이 함수 하나로 다음을 모두 해결할 수 있다.
- start_date == end_date 시 하루 전체 포함
- 타입 비교 오류 방지
- 다양한 모델에서 재사용 가능
정리
이 문제를 해결하면서 가장 크게 배운 건 “단순한 날짜 비교도 맥락이 중요하다”는 점이었다.
쿼리는 한 줄이지만, 그 안엔 타입, 시간대, 데이터 정밀도까지 모두 얽혀 있다.
겉보기엔 사소해 보여도, 실제 시스템에서는 이런 기본기가 데이터 정확도를 좌우한다.