2026년 10월 12일 | DBMS Error 가이드
이 글에서 다루는 내용
22008 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22008 datetime field overflow 는?
PostgreSQL 에러 코드 22008 (datetime field overflow)은 날짜/시간 값이 해당 데이터 타입이 허용하는 범위를 초과할 때 발생합니다. 예를 들어, DATE, TIMESTAMP, INTERVAL 등의 타입에 저장할 수 없는 극단적인 날짜 값이나 잘못된 연산 결과가 입력될 때 이 에러가 트리거됩니다. 실무에서는 날짜 연산 중 오버플로우, 잘못된 문자열 캐스팅, 또는 애플리케이션에서 잘못된 epoch 값을 전달할 때 자주 목격되는 에러입니다.
주요 발생 원인
- 날짜/시간 산술 연산 시 범위 초과
PostgreSQL의 TIMESTAMP 타입은 4713 BC부터 294276 AD까지의 범위를 지원합니다. 그러나 INTERVAL을 이용한 날짜 덧셈/뺄셈 연산에서 이 범위를 초과하는 결과가 나오면 즉시 22008 에러가 발생합니다. 예를 들어, 현재 시각에 수백만 년짜리 INTERVAL을 더하거나, 매우 작은 날짜 값에서 큰 INTERVAL을 빼는 경우가 대표적입니다. 배치 프로세스나 데이터 마이그레이션 스크립트에서 날짜 계산 로직에 버그가 있을 때 흔히 발생합니다.
- 잘못된 문자열을 날짜 타입으로 캐스팅
애플리케이션에서 날짜 문자열을 동적으로 생성하여 PostgreSQL에 전달할 때, 논리 오류로 인해 '9999-99-99'나 '2024-13-45'처럼 실제로 존재하지 않는 날짜 문자열이 만들어지는 경우가 있습니다. 이런 값을 TIMESTAMP 또는 DATE 컬럼에 삽입하거나 명시적으로 캐스팅하면 PostgreSQL은 즉시 22008 또는 유사한 날짜 관련 에러를 반환합니다. 특히 사용자 입력을 충분히 검증하지 않고 그대로 쿼리에 바인딩하는 코드에서 자주 목격됩니다.
- EXTRACT 또는 날짜 함수에서의 필드 범위 초과
MAKE_DATE(), MAKE_TIMESTAMP(), MAKE_INTERVAL() 등의 함수를 사용할 때 각 필드(월, 일, 시, 분, 초)에 허용 범위를 벗어나는 값을 전달하면 22008 에러가 발생합니다. 예를 들어 월에 13을 입력하거나, 초에 61 이상의 값을 넣는 경우가 해당됩니다. ETL 파이프라인에서 소스 시스템의 날짜 데이터 품질이 낮을 때 이런 문제가 빈번하게 나타납니다.
해결 방법
원인 1: 날짜 산술 연산 범위 초과 해결
연산 전에 결과값이 허용 범위 내에 있는지 확인하고, 범위를 벗어날 경우 NULLIF 또는 CASE 문으로 안전하게 처리합니다.
-- 문제가 되는 쿼리 예시
SELECT '4713-01-01 BC'::TIMESTAMP - INTERVAL '1 day';
-- ERROR: 22008: timestamp out of range
-- 해결책 1: CASE 문으로 범위 검사 후 연산
SELECT
CASE
WHEN target_date > '4713-01-02 BC'::DATE
THEN target_date - INTERVAL '1 day'
ELSE NULL
END AS safe_date
FROM my_table;
-- 해결책 2: 안전한 날짜 범위 클램핑 함수 생성
CREATE OR REPLACE FUNCTION safe_date_subtract(
p_date TIMESTAMP,
p_interval INTERVAL
) RETURNS TIMESTAMP AS $$
DECLARE
v_result TIMESTAMP;
BEGIN
BEGIN
v_result := p_date - p_interval;
EXCEPTION
WHEN datetime_field_overflow THEN
RAISE WARNING '날짜 오버플로우 발생: % - %', p_date, p_interval;
v_result := NULL;
END;
RETURN v_result;
END;
$$ LANGUAGE plpgsql;
-- 함수 사용 예시
SELECT safe_date_subtract(NOW(), INTERVAL '999999 years');
-- 에러 없이 NULL 반환
원인 2: 잘못된 문자열 캐스팅 해결
TO_TIMESTAMP() 함수와 예외 처리를 조합하여 유효하지 않은 날짜 문자열을 안전하게 핸들링합니다.
-- 문제가 되는 쿼리 예시
INSERT INTO orders (order_date) VALUES ('2024-13-45'::DATE);
-- ERROR: 22008: date/time field value out of range
-- 해결책 1: 정규식으로 날짜 형식 사전 검증
SELECT
raw_date_string,
CASE
WHEN raw_date_string ~ '^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$'
THEN raw_date_string::DATE
ELSE NULL
END AS validated_date
FROM staging_orders;
-- 해결책 2: PL/pgSQL 예외 처리를 통한 안전한 캐스팅 함수
CREATE OR REPLACE FUNCTION safe_to_date(
p_date_str TEXT,
p_format TEXT DEFAULT 'YYYY-MM-DD'
) RETURNS DATE AS $$
DECLARE
v_result DATE;
BEGIN
v_result := TO_DATE(p_date_str, p_format);
RETURN v_result;
EXCEPTION
WHEN datetime_field_overflow OR invalid_datetime_format THEN
RAISE WARNING '유효하지 않은 날짜 문자열: %', p_date_str;
RETURN NULL;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 실제 데이터 정제 시 활용 예시
UPDATE staging_orders
SET validated_date = safe_to_date(raw_date_string, 'YYYY-MM-DD')
WHERE raw_date_string IS NOT NULL;
-- 변환 실패 건 확인
SELECT raw_date_string
FROM staging_orders
WHERE raw_date_string IS NOT NULL
AND safe_to_date(raw_date_string) IS NULL;
원인 3: MAKE_DATE / MAKE_TIMESTAMP 함수 범위 초과 해결
각 날짜 필드의 유효 범위를 GREATEST/LEAST 함수나 CASE 문으로 클램핑하여 안전한 값을 보장합니다.
-- 문제가 되는 쿼리 예시
SELECT MAKE_DATE(2024, 13, 45);
-- ERROR: 22008: date field value out of range: 2024-13-45
-- 해결책: 각 필드 범위를 검증 후 MAKE_DATE 호출
CREATE OR REPLACE FUNCTION safe_make_date(
p_year INT,
p_month INT,
p_day INT
) RETURNS DATE AS $$
DECLARE
v_month INT;
v_day INT;
v_result DATE;
BEGIN
-- 월 범위 검사 (1~12)
IF p_month < 1 OR p_month > 12 THEN
RAISE WARNING '월 값 범위 초과: %', p_month;
RETURN NULL;
END IF;
-- 일 범위 검사 (1~해당 월 말일)
v_month := p_month;
IF p_day < 1 OR p_day > EXTRACT(DAY FROM
(MAKE_DATE(p_year, v_month, 1) + INTERVAL '1 month - 1 day')) THEN
RAISE WARNING '일 값 범위 초과: %-%-% ', p_year, p_month, p_day;
RETURN NULL;
END IF;
v_result := MAKE_DATE(p_year, p_month, p_day);
RETURN v_result;
EXCEPTION
WHEN datetime_field_overflow THEN
RAISE WARNING 'MAKE_DATE 오버플로우: %-%-% ', p_year, p_month, p_day;
RETURN NULL;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 사용 예시
SELECT safe_make_date(2024, 13, 45); -- NULL 반환
SELECT safe_make_date(2024, 12, 25); -- 2024-12-25 반환
예방 방법
- 입력 데이터 검증 레이어를 DB 레벨에 구현하라
애플리케이션에서 날짜 값을 검증하는 것만으로는 부족합니다. PostgreSQL 테이블에 CHECK 제약 조건을 추가하여 허용 범위 밖의 날짜가 절대 저장되지 않도록 이중으로 방어해야 합니다. 또한 DOMAIN 타입을 정의하여 재사용 가능한 날짜 유효성 검사 로직을 표준화하면 여러 테이블에 일관성 있게 적용할 수 있습니다.
“`sql
— CHECK 제약 조건으로 날짜 범위 제한
ALTER TABLE orders
ADD CONSTRAINT chk_order_date_range
CHECK (order_date BETWEEN ‘2000-01-01’ AND ‘2099-12-31’);
— 재사용 가능한 날짜 도메인 정의
CREATE DOMAIN business_date AS DATE
CHECK (VALUE BETWEEN ‘2000-01-01’ AND ‘2099-12-31’);
— 도메인 적용
CREATE TABLE transactions (
tx_id SERIAL PRIMARY KEY,
tx_date business_date NOT NULL
);
“`
- 날짜 연산이 포함된 쿼리에는 반드시 예외 처리 블록을 작성하라
복잡한 날짜 계산을 수행하는 PL/pgSQL 함수나 프로시저에는 EXCEPTION WHEN datetime_field_overflow 블록을 표준 패턴으로 추가해야 합니다. 이를 통해 예상치 못한 오버플로우가 발생해도 전체 트랜잭션이 롤백되지 않고, 해당 레코드만 건너뛰거나 로그에 기록하여 나중에 재처리할 수 있습니다.
“`sql
— 날짜 연산 표준 예외 처리 패턴
DO $$
DECLARE
v_date TIMESTAMP;
BEGIN
FOR v_date IN SELECT event_timestamp FROM raw_events LOOP
BEGIN
INSERT INTO processed_events
SELECT *, v_date + INTERVAL ’90 days’ AS expiry_date
FROM raw_events
WHERE event_timestamp = v_date;
EXCEPTION
WHEN datetime_field_overflow THEN
INSERT INTO error_log (error_code, error_msg, occurred_at)
VALUES (‘22008’, ‘날짜 오버플로우: ‘ || v_date::TEXT, NOW());
END;
END LOOP;
END;
$$;
“`
관련 에러
| 에러 코드 | 에러명 | 설명 |
|———–|——–|——|
| 22007 | invalid_datetime_format | 날짜/시간 문자열의 형식이 올바르지 않을 때 발생. 22008과 함께 자주 등장 |
| 22009 | invalid_time_zone_displacement_value | 시간대 오프셋 값이 허용 범위(-15:59 ~ +15:59)를 벗어날 때 발생 |
| 22003 | numeric_value_out_of_range | 숫자 타입의 범위 초과로, 날짜를 숫자로 변환하는 과정에서 함께 발생 가능 |
| 42883 | undefined_function | 존재하지 않는 날짜 함수를 호출할 때 발생하며 날짜 처리 코드 작성 시 혼동 주의 |
22008 에러는 특히 데이터 마이그레이션, 레거시 시스템 통합, 대용량 ETL 파이프라인 환경에서 빈번하게 발생하므로, 위에서 제시한 안전 함수 패턴과 CHECK 제약 조건을 반드시 프로젝트 표준으로 채택하여 적용하시기 바랍니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.