Search

REMINDER 03-02. TIMESTAMP 일자 검색은 종료일 미만의 범위를 쓰자

상황: startdbpg.tr_ord에서 2024년 1월 한 달간 발생한 주문을 조회한다. ord_dtm은 timestamp without time zone이며 tr_ord_x51 인덱스가 있다. 실데이터로 검증하면 잘못된 종료 조건 때문에 603건이 누락된다.

BAD SQL — BETWEEN의 종료값을 1월 31일 00:00:00으로 지정

EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) SELECT o.ord_no, o.ord_dtm FROM startdbpg.tr_ord o WHERE o.ord_dtm BETWEEN TIMESTAMP '2024-01-01 00:00:00' AND TIMESTAMP '2024-01-31 00:00:00';
SQL
복사
실측 결과 (BAD): 16,849건
Index Scan using tr_ord_x51 on tr_ord o (actual time=0.761..124.674 rows=16849 loops=1) Index Cond: ((ord_dtm >= '2024-01-01 00:00:00'::timestamp without time zone) AND (ord_dtm <= '2024-01-31 00:00:00'::timestamp without time zone)) Buffers: shared hit=252 Planning: Buffers: shared hit=127 Planning Time: 0.642 ms Execution Time: 125.914 ms
Plain Text
복사

BAD SQL 추가 — TO_CHAR로 날짜를 문자열로 변환해 비교

SELECT o.ord_no ,o.ord_dtm FROM startdbpg.tr_ord o WHERE TO_CHAR(o.ord_dtm, 'YYYYMM') = '202401';
SQL
복사
•
정확성: 2024년 1월의 주문을 날짜 경계 누락 없이 찾을 수 있다. 첫 번째 BAD처럼 1월 31일 주문을 누락하는 문제는 없다.
•
성능: 인덱스 컬럼 ord_dtm을 TO_CHAR로 가공했기 때문에 tr_ord_x51(ord_dtm)의 일반적인 범위 탐색 조건(Index Cond)으로 바로 사용하기 어렵다. 많은 행에 함수가 적용되거나 전체 스캔이 발생할 수 있다.
•
개선: 컬럼은 가공하지 않고 아래 GOOD처럼 ord_dtm >= 시작일 AND ord_dtm < 다음 달 시작일로 검색한다.
•
이 추가 사례는 설명용 SQL이며, 해당 SQL의 실행계획·실행시간을 새로 측정한 것은 아니다. 표현식 인덱스가 따로 있거나 실행 환경이 다르면 접근 경로도 달라질 수 있다.

GOOD SQL — 2월 1일 00:00:00 미만까지 포함하는 반열린 범위

EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) SELECT o.ord_no, o.ord_dtm FROM startdbpg.tr_ord o WHERE o.ord_dtm >= TIMESTAMP '2024-01-01 00:00:00' AND o.ord_dtm < TIMESTAMP '2024-02-01 00:00:00';
SQL
복사
실측 결과 (GOOD): 17,452건
Index Scan using tr_ord_x51 on tr_ord o (actual time=0.082..11.809 rows=17452 loops=1) Index Cond: ((ord_dtm >= '2024-01-01 00:00:00'::timestamp without time zone) AND (ord_dtm < '2024-02-01 00:00:00'::timestamp without time zone)) Buffers: shared hit=260 Planning: Buffers: shared hit=124 Planning Time: 0.636 ms Execution Time: 13.250 ms
Plain Text
복사
누락 건수 직접 검증
SELECT COUNT(*) AS missed_cnt FROM startdbpg.tr_ord o WHERE o.ord_dtm > TIMESTAMP '2024-01-31 00:00:00' AND o.ord_dtm < TIMESTAMP '2024-02-01 00:00:00';
SQL
복사
실행 결과: 603건. 예를 들어 ord_no=523831의 주문일시는 2024-01-31 06:32:00으로 GOOD에는 포함되지만 BAD에서는 제외된다.

핵심

•
BAD 1 — 결과 정확성 문제: BETWEEN은 시작과 종료값을 모두 포함한다. TIMESTAMP의 종료일을 00:00:00으로 지정하면 그날 자정 이후 데이터가 누락된다.
•
BAD 2 — 접근 효율 문제: TO_CHAR(ord_dtm, 'YYYYMM')는 해당 월을 정확히 검색할 수 있지만 인덱스 컬럼 가공으로 일반 인덱스 범위 탐색이 어려워진다.
•
GOOD — 정확성과 인덱스 활용을 함께 고려: 컬럼을 가공하지 않고 >= 시작일 AND < 다음달 시작일 형태로 검색하자.
실행계획 해석: BAD/GOOD 모두 tr_ord_x51의 Index Scan + Index Cond를 사용한다. 이번 사례는 인덱스 사용 여부가 아니라 검색 경계와 결과 건수의 정확성을 보여준다. 실행시간 125.914ms와 13.250ms는 각각 한 번 측정한 값으로, 캐시·실행환경 등에 영향을 받으므로 GOOD의 보편적인 성능 우위를 증명하지 않는다.
💡
참고·예외
•
조회할 업무 기간과 시간대 기준을 먼저 확정한다. timestamp without time zone과 timestamptz의 의미는 다르며, DATE형 컬럼과도 구분해서 사용한다.