2026년 06월 16일 | DBMS Error 가이드
이 글에서 다루는 내용
22031 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22031 invalid argument for sql json datetime function 는?
PostgreSQL 에러 코드 22031은 SQL/JSON 경로 표현식에서 날짜/시간 관련 함수(datetime())를 사용할 때, 함수에 전달된 인자가 유효하지 않은 형식이거나 변환할 수 없는 값일 때 발생합니다. 주로 jsonb_path_query, jsonb_path_exists, @?, @@ 같은 JSON 경로 연산자와 함께 datetime() 함수를 사용하는 쿼리에서 나타납니다. 이 에러는 JSON 데이터 내 날짜 문자열이 ISO 8601 표준을 따르지 않거나, 잘못된 템플릿 포맷이 제공될 때 특히 자주 발생합니다.
주요 발생 원인
1. JSON 내 날짜 문자열이 유효하지 않은 형식일 때
JSON 필드에 저장된 날짜 문자열이 ISO 8601 표준 형식(YYYY-MM-DD, YYYY-MM-DDTHH:MI:SS 등)을 따르지 않으면 datetime() 함수가 파싱에 실패합니다. 예를 들어 "2024/08/15" 처럼 슬래시 구분자를 사용하거나, "15-08-2024" 처럼 일-월-년 순서로 저장된 경우가 대표적입니다. PostgreSQL의 datetime() 함수는 기본적으로 ISO 8601 포맷을 기대하므로, 그 외 포맷은 모두 에러를 유발할 수 있습니다.
2. datetime() 함수에 잘못된 템플릿 포맷 문자열을 전달했을 때
PostgreSQL 15 이상에서는 datetime("template") 형식으로 포맷 지정자를 함께 넘길 수 있습니다. 이때 템플릿 문자열이 실제 JSON 날짜 데이터의 구조와 일치하지 않거나, 지원되지 않는 포맷 패턴을 사용하면 22031 에러가 발생합니다. 예를 들어 datetime("DD/MM/YYYY") 형태로 넘겼는데 실제 데이터가 다른 구조일 경우 파싱이 실패합니다.
3. NULL 또는 비문자열 타입 값이 datetime()에 전달될 때
JSON 경로 표현식에서 datetime() 함수를 호출하는 대상 필드가 실제로는 숫자(123456)나 불리언(true), 또는 배열/객체 타입일 경우에도 이 에러가 발생할 수 있습니다. JSON 구조가 불균일하여 일부 레코드는 날짜 문자열을 갖고 있지만, 다른 레코드는 전혀 다른 타입의 값을 가진 경우가 실무에서 빈번하게 발생합니다. 이런 상황에서 방어 코드 없이 datetime()을 호출하면 에러가 불가피합니다.
해결 방법
원인 1 해결: 날짜 형식 정규화
JSON 데이터를 삽입하기 전에 날짜 형식을 ISO 8601로 통일하거나, 쿼리 시점에 jsonb_path_query_first와 캐스팅을 조합합니다.
-- 잘못된 예: 슬래시 구분자 날짜가 들어있는 경우
SELECT jsonb_path_query(
'{"created_at": "2024/08/15"}',
'$.created_at.datetime()'
);
-- ERROR: 22031 invalid argument for sql json datetime function
-- 해결: to_date 또는 regexp_replace 로 포맷 변환 후 삽입
UPDATE events
SET payload = jsonb_set(
payload,
'{created_at}',
to_jsonb(
to_date(payload->>'created_at', 'YYYY/MM/DD')::text
)
)
WHERE payload->>'created_at' ~ '^\d{4}/\d{2}/\d{2}$';
-- 변환 후 정상 조회
SELECT jsonb_path_query(
'{"created_at": "2024-08-15"}',
'$.created_at.datetime()'
);
원인 2 해결: datetime() 템플릿 포맷 정확히 지정 (PostgreSQL 15+)
-- 잘못된 템플릿 예
SELECT jsonb_path_query(
'{"event_time": "15/08/2024"}',
'$.event_time.datetime("YYYY-MM-DD")' -- 데이터와 포맷 불일치
);
-- ERROR: 22031
-- 올바른 템플릿 지정
SELECT jsonb_path_query(
'{"event_time": "15/08/2024"}',
'$.event_time.datetime("DD/MM/YYYY")' -- 데이터 구조에 맞게 수정
);
-- 실무 패턴: 여러 포맷을 순차적으로 시도
SELECT
COALESCE(
jsonb_path_query_first(payload, '$.ts.datetime("YYYY-MM-DD")'),
jsonb_path_query_first(payload, '$.ts.datetime("DD/MM/YYYY")')
) AS parsed_date
FROM events;
원인 3 해결: 타입 검사 후 datetime() 호출
-- 방어 코드 없이 호출하는 위험한 패턴
SELECT jsonb_path_query(data, '$.timestamp.datetime()')
FROM logs;
-- 일부 행에서 22031 발생 가능
-- 안전한 패턴 1: jsonb_typeof로 타입 사전 필터링
SELECT jsonb_path_query(data, '$.timestamp.datetime()')
FROM logs
WHERE jsonb_typeof(data->'timestamp') = 'string'
AND data->>'timestamp' ~ '^\d{4}-\d{2}-\d{2}';
-- 안전한 패턴 2: jsonb_path_query_first + IS NOT NULL 조합
SELECT
id,
jsonb_path_query_first(data, '$.timestamp ? (@ != null).datetime()') AS ts
FROM logs
WHERE jsonb_path_exists(data, '$.timestamp ? (@.type() == "string")');
-- 안전한 패턴 3: 예외 처리를 위한 함수 래핑
CREATE OR REPLACE FUNCTION safe_json_datetime(p_json jsonb, p_path text)
RETURNS timestamptz
LANGUAGE plpgsql
AS $$
DECLARE
v_result timestamptz;
BEGIN
SELECT jsonb_path_query_first(p_json, p_path::jsonpath)::text::timestamptz
INTO v_result;
RETURN v_result;
EXCEPTION
WHEN SQLSTATE '22031' THEN
RETURN NULL;
WHEN OTHERS THEN
RETURN NULL;
END;
$$;
-- 함수 활용 예
SELECT id, safe_json_datetime(data, '$.created_at.datetime()') AS created_at
FROM events;
예방 방법
1. JSON 데이터 삽입 시 날짜 형식 유효성 검사 제약 조건 추가
테이블 설계 단계에서 CHECK 제약 조건 또는 트리거를 사용하여 JSON 날짜 필드가 항상 올바른 ISO 8601 형식으로 저장되도록 강제합니다. 이렇게 하면 데이터 품질을 소스 시점에 보장할 수 있어 런타임 에러를 사전에 차단할 수 있습니다.
-- CHECK 제약 조건으로 날짜 형식 강제
ALTER TABLE events
ADD CONSTRAINT chk_payload_date_format
CHECK (
payload->>'created_at' IS NULL
OR payload->>'created_at' ~ '^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2})?'
);
-- 또는 도메인을 활용한 재사용 가능한 검증
CREATE DOMAIN iso_date_string AS text
CHECK (VALUE ~ '^\d{4}-\d{2}-\d{2}$');
2. JSON 경로 쿼리 전 데이터 프로파일링 및 모니터링 파이프라인 구축
datetime() 함수를 사용하는 쿼리를 프로덕션에 배포하기 전, 반드시 실제 데이터를 대상으로 프로파일링을 수행하여 비정상 날짜 형식의 비율과 분포를 파악합니다. 정기적인 데이터 품질 검사 쿼리를 cron 작업이나 pg_cron으로 스케줄링하면 데이터 이상을 조기에 감지할 수 있습니다.
-- 날짜 형식 이상 데이터 사전 탐지 쿼리
SELECT
COUNT(*) FILTER (WHERE payload->>'created_at' ~ '^\d{4}-\d{2}-\d{2}') AS valid_iso,
COUNT(*) FILTER (WHERE payload->>'created_at' !~ '^\d{4}-\d{2}-\d{2}'
AND payload->>'created_at' IS NOT NULL) AS invalid_format,
COUNT(*) FILTER (WHERE payload->>'created_at' IS NULL) AS missing
FROM events;
관련 에러
- 22007 (invalid_datetime_format):
CAST또는TO_TIMESTAMP같은 일반 날짜 변환 함수에서 포맷 불일치 시 발생. 22031과 유사하지만 SQL/JSON 경로 문맥이 아닌 일반 SQL에서 발생한다는 차이가 있습니다. - 22P02 (invalid_text_representation): 문자열을 날짜/시간 타입으로 직접 캐스팅할 때 파싱 실패 시 발생하며, JSON 값을
::timestamp로 캐스팅하는 과정에서 자주 동반됩니다. - 2203A (too many JSON array elements) / 2203B (too many JSON object members): JSON 경로 함수 사용 시 함께 발생할 수 있는 관련 SQL/JSON 에러 코드로, 복잡한 JSON 경로 처리 시 주의가 필요합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.