프로젝트를 진행하면서 단일 날짜(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 시점에 정확히 일치하는 데이터만 포함되고,
그 날짜의 나머지 레코드는 모두 누락되었다.


원인 분석

이 문제는 단순히 비교 연산자(<=) 문제처럼 보였지만, 실제로는 여러 요인이 복합적으로 작용했다.

  1. 범위 누락
    • start_date <= created_at <= end_date는 시각이 정확히 00:00:00인 데이터만 포함한다.
    • 즉, 하루 전체(00:00:00 ~ 23:59:59)를 포함하지 못한다.
  2. 타입 불일치
    • 입력된 start_date / end_date는 date 타입
    • DB의 created_at은 datetime 타입
    • SQLAlchemy 비교 시 타입이 달라 미묘한 오차가 발생할 수 있다.
  3. 시간대(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 변환 후 비교하는 방식이 안전하다.

디버깅 체크포인트

문제를 해결하는 과정에서 아래와 같은 점검 단계를 거쳤다.

  1. SQLAlchemy 쿼리를 실제 SQL 문자열로 출력해 확인
  2. print(str(query))
  3. start_date / end_date 값과 DB created_at 실제 저장 값을 직접 비교
  4. 단일 날짜 조회 시 < start_date + 1 day 조건이 올바르게 작동하는지 검증
  5. DB와 애플리케이션 간 시간대 차이 확인

구현하면서 배운 점

  1. date와 datetime의 차이를 명확히 이해해야 한다.
    단순 비교라도 타입이 다르면 의도치 않은 결과가 나온다.
  2. 단일 날짜 조회도 결국 "범위 조회"라는 사실을 깨달았다.
    start_date == end_date라고 해도 하루 전체를 포함하는 로직이 필요하다.
  3. 공통 패턴은 헬퍼 함수로 추상화하면 유지보수가 훨씬 쉬워진다.
  4. 시간대(Timezone)는 평소엔 잘 안 보이지만, 실서비스에선 반드시 고려해야 하는 요소다.
  5. 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 시 하루 전체 포함
  • 타입 비교 오류 방지
  • 다양한 모델에서 재사용 가능

정리

이 문제를 해결하면서 가장 크게 배운 건 “단순한 날짜 비교도 맥락이 중요하다”는 점이었다.
쿼리는 한 줄이지만, 그 안엔 타입, 시간대, 데이터 정밀도까지 모두 얽혀 있다.
겉보기엔 사소해 보여도, 실제 시스템에서는 이런 기본기가 데이터 정확도를 좌우한다.

복사했습니다!