PostgreSQL 2203A 오류 원인과 해결 방법 완벽 가이드

2203A
2026년 06월 19일 | DBMS Error 가이드

이 글에서 다루는 내용

2203A 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

2203A sql json member not found 는?

PostgreSQL 에러 코드 2203Asql_json_member_not_found 에러로, SQL/JSON 경로 표현식(path expression)을 사용하여 JSON 데이터에서 특정 멤버(키)를 조회할 때 해당 멤버가 존재하지 않을 경우 발생합니다. 이 에러는 주로 jsonb_path_query, jsonb_path_value, JSON_VALUE, JSON_QUERY 등의 함수나 @>, ->, ->> 연산자를 사용하는 쿼리에서 strict 모드로 JSON 경로를 탐색할 때 나타납니다. PostgreSQL 14 이상에서 SQL 표준 JSON 함수들이 강화되면서 strict 모드의 경로 탐색 중 존재하지 않는 키를 참조하면 단순히 NULL을 반환하지 않고 이 에러를 발생시키도록 동작이 변경되었습니다.


주요 발생 원인

1. strict 모드에서 존재하지 않는 JSON 키 참조

가장 흔한 원인입니다. SQL/JSON 경로 표현식에서 strict 키워드를 명시하거나 기본 동작이 strict인 함수를 사용할 때, JSON 객체 내에 해당 키가 없으면 즉시 에러가 발생합니다. lax 모드와 달리 strict 모드는 존재하지 않는 멤버를 허용하지 않고 예외를 발생시키도록 설계되어 있어, 데이터 품질이 균일하지 않은 JSON 컬럼에 strict 모드를 적용하면 문제가 됩니다.

2. JSON 데이터 구조 불일치 (스키마리스 데이터)

JSON은 스키마가 없는(schemaless) 자료구조이기 때문에, 동일한 테이블의 같은 컬럼이라도 행마다 JSON 구조가 다를 수 있습니다. 예를 들어 일부 레코드에는 address.city 키가 존재하지만 다른 레코드에는 address 자체가 없거나 city 대신 town으로 저장된 경우, 경로 탐색 시 해당 멤버를 찾지 못하는 상황이 발생합니다. 이는 외부 API 응답, 이벤트 로그, 반정형 데이터를 저장하는 시스템에서 특히 자주 발생합니다.

3. JSON_VALUE / JSON_QUERY 함수의 기본 에러 처리 설정

PostgreSQL 14+에서 새롭게 도입된 SQL 표준 함수인 JSON_VALUEJSON_QUERY는 기본적으로 ERROR ON ERROR 또는 ERROR ON EMPTY 옵션이 적용되어, 경로가 빈 결과를 반환하거나 에러가 발생하면 예외를 던집니다. 이전 버전의 jsonb 연산자 방식에 익숙한 개발자가 새 함수를 사용하면서 에러 처리 절을 누락하면 예상치 못한 2203A 에러를 마주하게 됩니다.


해결 방법

원인 1 해결: lax 모드 사용 또는 존재 여부 사전 확인

strict 모드 대신 lax 모드를 사용하면 존재하지 않는 멤버에 대해 NULL을 반환합니다.

-- 문제 발생 예시 (strict 모드)
SELECT jsonb_path_query('{"name": "Alice"}'::jsonb, 'strict $.address.city');
-- ERROR: 2203A: SQL/JSON member not found

-- 해결책 1: lax 모드 사용
SELECT jsonb_path_query_first(
    '{"name": "Alice"}'::jsonb,
    'lax $.address.city'
);
-- 결과: NULL (에러 없음)

-- 해결책 2: 경로 존재 여부 사전 확인
SELECT CASE
    WHEN jsonb_path_exists(data, '$.address.city')
    THEN jsonb_path_query_first(data, '$.address.city')::text
    ELSE '알 수 없음'
END AS city
FROM users;

원인 2 해결: 조건부 쿼리 및 기본값 처리

JSON 데이터 구조가 불일치할 경우, 키 존재 여부를 확인한 후 안전하게 값을 추출합니다.

-- 테스트 테이블 생성
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    payload JSONB
);

INSERT INTO events (payload) VALUES
    ('{"user": {"name": "Bob", "email": "bob@example.com"}}'),
    ('{"user": {"name": "Carol"}}'),  -- email 키 없음
    ('{"action": "logout"}');          -- user 키 자체 없음

-- 안전한 값 추출: COALESCE와 ->> 연산자 활용
SELECT
    id,
    payload -> 'user' ->> 'name' AS username,
    COALESCE(payload -> 'user' ->> 'email', 'N/A') AS email
FROM events;

-- jsonb_path_query_first를 활용한 안전한 추출
SELECT
    id,
    jsonb_path_query_first(payload, 'lax $.user.name')::text AS username,
    COALESCE(
        jsonb_path_query_first(payload, 'lax $.user.email')::text,
        'N/A'
    ) AS email
FROM events;

원인 3 해결: JSON_VALUE / JSON_QUERY 에러 처리 절 추가

SQL 표준 함수를 사용할 때 DEFAULT ... ON ERRORNULL ON EMPTY를 명시적으로 지정합니다.

-- 문제 발생 예시
SELECT JSON_VALUE('{"name": "Dave"}'::jsonb, '$.age');
-- ERROR: 2203A (strict 기본 동작)

-- 해결책: ON EMPTY와 ON ERROR 절 명시
SELECT JSON_VALUE(
    '{"name": "Dave"}'::jsonb,
    '$.age'
    DEFAULT -1 ON EMPTY
    DEFAULT -1 ON ERROR
) AS age;
-- 결과: -1

-- JSON_QUERY에서도 동일하게 적용
SELECT JSON_QUERY(
    '{"name": "Dave"}'::jsonb,
    '$.address'
    NULL ON EMPTY
    NULL ON ERROR
) AS address;
-- 결과: NULL

-- 실제 업무 활용 예시: 다양한 구조의 로그 데이터 처리
SELECT
    id,
    JSON_VALUE(
        payload,
        '$.user.id'
        DEFAULT 'anonymous' ON EMPTY
        DEFAULT 'error' ON ERROR
    ) AS user_id,
    JSON_VALUE(
        payload,
        '$.event.type'
        NULL ON EMPTY
        NULL ON ERROR
    ) AS event_type
FROM events;

추가 해결책: 함수 래핑으로 안전한 JSON 추출 함수 생성

반복적으로 사용되는 패턴이라면 래퍼 함수를 만들어 두면 편리합니다.

-- 안전한 JSON 경로 추출 함수
CREATE OR REPLACE FUNCTION safe_json_extract(
    p_data JSONB,
    p_path TEXT,
    p_default TEXT DEFAULT NULL
)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
BEGIN
    RETURN COALESCE(
        jsonb_path_query_first(p_data, p_path::jsonpath)::text,
        p_default
    );
EXCEPTION
    WHEN sqlstate '2203A' THEN
        RETURN p_default;
    WHEN OTHERS THEN
        RETURN p_default;
END;
$$;

-- 사용 예시
SELECT safe_json_extract(
    '{"user": {"name": "Eve"}}'::jsonb,
    '$.user.email',
    'no-email@default.com'
) AS email;
-- 결과: 'no-email@default.com'

예방 방법

1. JSON 스키마 검증 제약조건 적용

JSON 데이터를 삽입하기 전에 CHECK 제약조건이나 트리거를 활용하여 필수 키의 존재를 보장합니다. 이렇게 하면 데이터 적재 시점에 구조적 일관성을 확보할 수 있어, 이후 쿼리에서 발생할 수 있는 2203A 에러를 근본적으로 예방할 수 있습니다.

-- CHECK 제약조건으로 필수 JSON 키 검증
ALTER TABLE events
ADD CONSTRAINT chk_payload_has_user
CHECK (
    payload ? 'user'
    AND payload -> 'user' ? 'name'
);

-- 또는 JSON Schema 유효성 검사 함수 활용 (pg_jsonschema 확장)
-- CREATE EXTENSION pg_jsonschema;
-- ALTER TABLE events
-- ADD CONSTRAINT chk_payload_schema
-- CHECK (jsonb_matches_schema(
--     '{"type": "object", "required": ["user"], "properties": {"user": {"type": "object", "required": ["name"]}}}'::json,
--     payload
-- ));

2. 쿼리 작성 시 lax 모드와 방어적 코딩 표준화

팀 내 SQL 작성 가이드라인에 JSON 경로 표현식은 기본적으로 lax 모드를 사용하고, COALESCE와 기본값 처리를 의무화하는 규칙을 수립합니다. strict 모드는 데이터 품질이 완전히 보장된 경우에만 의도적으로 선택하도록 코드 리뷰 체크리스트에 포함시켜, 예상치 못한 에러가 운영 환경에 노출되지 않도록 합니다.


관련 에러

  • 22032 (invalid_json_text): JSON 문자열 자체의 형식이 잘못된 경우 발생하며, 2203A와 달리 파싱 단계에서 실패합니다.
  • 2203B (sql_json_array_not_found): JSON 경로에서 배열이 기대되는 위치에 배열이 없을 때 발생하는 유사 에러로, 멤버 탐색 대신 배열 탐색 시 발생합니다.
  • 2203C (sql_json_scalar_required): JSON 경로 결과가 스칼라 값이어야 하는데 객체나 배열이 반환될 때 발생합니다.
  • 2203D (sql_json_item_cannot_be_cast_to_target_type): JSON 값을 특정 타입으로 캐스팅할 수 없을 때 발생하며, JSON_VALUERETURNING 절과 함께 자주 나타납니다.
  • 22034 (no_sql_json_item): SQL/JSON 시퀀스에 아이템이 없을 때 발생하는 에러로, 빈 결과 처리와 관련이 있습니다.

이 에러들은 모두 PostgreSQL의 SQL/JSON 기능 관련 에러 패밀리(22번대)에 속하므로, JSON 경로 탐색 로직을 작성할 때는 이 에러 코드들을 함께 고려하여 예외 처리를 구현하는 것이 좋습니다.


DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기