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

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

이 글에서 다루는 내용

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

2203B sql json number not found 는?

PostgreSQL 에러 코드 2203B는 SQL/JSON 경로 표현식을 사용할 때, 해당 경로에서 숫자(number) 값을 찾을 수 없을 때 발생하는 오류입니다. 주로 jsonb_path_query, jsonb_path_exists, JSON_VALUE, JSON_QUERY 등의 SQL/JSON 함수에서 숫자 타입을 기대하는 경로 표현식이 숫자가 아닌 값(문자열, 배열, 객체 등)을 만나거나, 해당 키 자체가 존재하지 않을 때 트리거됩니다. 이 에러는 PostgreSQL 14 이후 SQL 표준 JSON 함수가 강화되면서 더욱 명시적으로 분리된 에러 코드로 관리되고 있으며, 데이터 정합성 문제나 잘못된 JSON 경로 설계로 인해 운영 환경에서 빈번하게 발생할 수 있습니다.


주요 발생 원인

1. JSON 경로가 가리키는 값이 숫자가 아닌 다른 타입인 경우

SQL/JSON 경로 표현식에서 .double(), .integer(), .number() 같은 숫자 변환 메서드를 사용할 때, 실제 JSON 필드의 값이 문자열이거나 boolean, null인 경우 이 에러가 발생합니다. 예를 들어 "price": "free" 처럼 숫자여야 할 필드가 문자열로 저장된 경우, 이를 숫자로 변환하려 하면 PostgreSQL은 즉시 2203B 에러를 반환합니다. 이는 JSON 스키마가 강제되지 않는 NoSQL 스타일 저장 방식의 대표적인 함정입니다.

2. JSON 경로 자체가 존재하지 않는 키를 참조하는 경우

$.store.price와 같은 경로를 사용할 때, store 객체 자체가 없거나 price 키가 누락된 경우 경로 탐색이 실패하여 숫자를 찾지 못하는 상황이 됩니다. 특히 선택적(optional) 필드를 포함하는 JSON 문서를 다룰 때, 방어적인 경로 검사 없이 바로 숫자 변환을 시도하면 에러가 발생합니다. 이는 애플리케이션 레벨에서 JSON 구조 변경이 이루어졌을 때, DB 쿼리가 업데이트되지 않아 발생하는 경우가 매우 많습니다.

3. JSON 배열에서 인덱스 접근 실수 또는 빈 배열 참조

$.items[0].qty처럼 배열의 특정 인덱스를 통해 숫자 값을 가져오려 할 때, 해당 배열이 비어있거나 인덱스가 범위를 벗어나면 반환값이 없어 숫자를 찾을 수 없는 상태가 됩니다. 또한 배열 요소 자체가 숫자가 아닌 중첩 객체일 경우에도 동일한 에러가 발생합니다. 대용량 이벤트 로그나 주문 데이터를 JSON 배열로 저장하는 시스템에서 특히 자주 발생하는 패턴입니다.


해결 방법

원인 1 해결: 타입 확인 후 안전하게 숫자 변환

jsonb_typeof() 함수로 먼저 타입을 확인하거나, jsonb_path_exists()로 경로가 숫자인지 검증한 후 처리합니다.

-- 문제가 되는 쿼리 예시
SELECT jsonb_path_query('{"price": "free"}', '$.price.double()');
-- 에러: SQL/JSON number not found (2203B)

-- 해결책 1: jsonb_typeof로 타입 확인 후 처리
SELECT
    CASE
        WHEN jsonb_typeof(data->'price') = 'number'
        THEN (data->>'price')::numeric
        ELSE NULL
    END AS safe_price
FROM products;

-- 해결책 2: jsonb_path_exists로 사전 검증
SELECT jsonb_path_query(
    '{"price": "free"}',
    '$.price ? (@ != null)'
);

-- 해결책 3: JSON_VALUE with ERROR ON ERROR 대신 DEFAULT 사용 (PostgreSQL 16+)
SELECT JSON_VALUE(
    data,
    '$.price' RETURNING numeric
    DEFAULT 0 ON ERROR
)
FROM products;

원인 2 해결: 존재하지 않는 키에 대한 방어적 처리

-- 문제가 되는 쿼리: price 키가 없는 경우 에러 발생
SELECT jsonb_path_query(
    '{"name": "item_a"}',
    '$.price.double()'
);

-- 해결책 1: ? 연산자로 키 존재 여부 먼저 확인
SELECT
    CASE
        WHEN data ? 'price' AND jsonb_typeof(data->'price') = 'number'
        THEN (data->>'price')::numeric
        ELSE 0.00
    END AS price
FROM products;

-- 해결책 2: jsonb_path_query_array로 빈 결과 허용
SELECT jsonb_path_query_array(
    '{"name": "item_a"}',
    '$.price'
);
-- 결과: [] (에러 없이 빈 배열 반환)

-- 해결책 3: COALESCE와 함께 NULL 안전 처리
SELECT COALESCE(
    (data #>> '{store, price}')::numeric,
    0
) AS price
FROM products;

-- 해결책 4: jsonb_path_query_first 사용 (결과 없으면 NULL 반환)
SELECT jsonb_path_query_first(
    '{"name": "item_a"}',
    '$.price'
) AS price
FROM products;

원인 3 해결: 배열 인덱스 접근 안전화

-- 문제가 되는 쿼리: 빈 배열에서 인덱스 접근
SELECT jsonb_path_query(
    '{"items": []}',
    '$.items[0].qty.integer()'
);

-- 해결책 1: 배열 길이 확인 후 접근
SELECT
    CASE
        WHEN jsonb_array_length(data->'items') > 0
             AND jsonb_typeof(data->'items'->0->'qty') = 'number'
        THEN (data->'items'->0->>'qty')::integer
        ELSE 0
    END AS first_qty
FROM orders;

-- 해결책 2: jsonb_path_query로 배열 전체를 안전하게 처리
SELECT
    jsonb_path_query_array(data, '$.items[*].qty ? (@ != null)')
AS quantities
FROM orders;

-- 해결책 3: 배열 언네스트 후 타입 필터링
SELECT
    elem->>'qty' AS qty
FROM orders,
     jsonb_array_elements(data->'items') AS elem
WHERE jsonb_typeof(elem->'qty') = 'number';

-- 실무 종합 예제: 안전한 JSON 숫자 추출 함수 만들기
CREATE OR REPLACE FUNCTION safe_json_number(
    p_data jsonb,
    p_path text
) RETURNS numeric AS $$
DECLARE
    v_result jsonb;
BEGIN
    v_result := jsonb_path_query_first(p_data, p_path::jsonpath);
    IF v_result IS NULL OR jsonb_typeof(v_result) != 'number' THEN
        RETURN NULL;
    END IF;
    RETURN (v_result #>> '{}')::numeric;
EXCEPTION
    WHEN SQLSTATE '2203B' THEN
        RETURN NULL;
    WHEN OTHERS THEN
        RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;

-- 함수 사용 예
SELECT safe_json_number(
    '{"price": "free", "discount": 10}'::jsonb,
    '$.price'
);
-- 결과: NULL (에러 없음)

SELECT safe_json_number(
    '{"price": "free", "discount": 10}'::jsonb,
    '$.discount'
);
-- 결과: 10

예방 방법

1. JSON 컬럼에 CHECK 제약 조건 또는 도메인 타입 적용으로 데이터 정합성 강제화

운영 테이블에 JSON/JSONB 컬럼이 있다면, 반드시 숫자여야 하는 필드는 삽입 시점부터 CHECK 제약 조건을 통해 타입을 강제하는 것이 가장 근본적인 예방책입니다. 이를 통해 2203B 에러의 원인 자체를 데이터 계층에서 차단할 수 있습니다.

-- CHECK 제약으로 price 필드가 반드시 숫자임을 강제
ALTER TABLE products
ADD CONSTRAINT chk_price_is_number
CHECK (
    (data->'price') IS NULL
    OR jsonb_typeof(data->'price') = 'number'
);

-- 트리거를 이용한 사전 검증 (복잡한 구조의 경우)
CREATE OR REPLACE FUNCTION validate_product_json()
RETURNS trigger AS $$
BEGIN
    IF NEW.data ? 'price' AND jsonb_typeof(NEW.data->'price') != 'number' THEN
        RAISE EXCEPTION 'price 필드는 반드시 숫자여야 합니다. 입력값: %',
            NEW.data->>'price';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_validate_product_json
BEFORE INSERT OR UPDATE ON products
FOR EACH ROW EXECUTE FUNCTION validate_product_json();

2. SQL/JSON 경로 쿼리 작성 시 항상 방어적 패턴(Defensive Pattern) 채택

jsonb_path_query 계열 함수를 사용할 때는 항상 jsonb_path_query_first 또는 jsonb_path_query_array를 우선 고려하여 결과가 없을 때 에러 대신 NULL이나 빈 배열을 반환하도록 작성합니다. 또한 EXCEPTION WHEN SQLSTATE '2203B' 블록을 PL/pgSQL 함수에 포함시켜 런타임 오류가 서비스 전체에 영향을 미치지 않도록 방어 계층을 추가하는 것이 실무 Best Practice입니다.


관련 에러

  • 22032 (invalid_json_text): JSON 문자열 자체의 파싱 오류로, 올바른 JSON 형식이 아닐 때 발생합니다.
  • 22033 (invalid_sql_json_subscript): SQL/JSON 경로에서 잘못된 배열 서브스크립트를 사용할 때 발생합니다.
  • 22034 (more_than_one_sql_json_item): 경로 표현식이 하나의 값만 기대하는 컨텍스트에서 여러 결과를 반환할 때 발생합니다.
  • 22035 (no_sql_json_item): 경로 표현식의 결과가 아예 없을 때 발생하며, 2203B와 함께 JSON 경로 탐색 실패의 대표적인 에러 코드입니다.
  • 2203C (sql_json_string_not_found): 숫자 대신 문자열을 찾을 수 없을 때 발생하는 에러로, 2203B와 쌍을 이루는 에러입니다.
  • 2203F (sql_json_item_cannot_be_cast_to_target_type): JSON 값을 대상 타입으로 캐스팅할 수 없을 때 발생하며, 숫자 변환 실패와 밀접하게 연관됩니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기