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

22036
2026년 08월 22일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22036 non numeric sql json item 는?

PostgreSQL 에러 코드 22036non numeric sql json item 에러로, JSON/JSONB 데이터에서 숫자 연산이나 수치형 비교를 수행하려 할 때 해당 JSON 항목이 실제로는 숫자가 아닌 문자열, 불리언, 배열, 객체 등의 타입일 경우 발생합니다. 주로 SQL/JSON 경로 표현식(JSON Path Expression)을 사용하는 jsonb_path_query, @?, @@ 등의 연산자 또는 함수에서 발생하며, PostgreSQL 12 버전부터 도입된 SQL/JSON 표준 기능을 활용할 때 자주 마주치는 에러입니다. 데이터 입력 시 타입 검증이 충분하지 않거나, 외부 시스템에서 전달된 JSON 데이터의 타입이 기대와 다를 때 운영 환경에서 예기치 않게 나타날 수 있습니다.


주요 발생 원인

1. JSON 경로 표현식에서 숫자 연산 대상이 문자열인 경우

가장 빈번한 원인으로, JSON 데이터 내 숫자처럼 보이는 값이 실제로는 따옴표로 감싸진 문자열 타입("123")인 경우입니다. JSON Path 표준에서 산술 연산(+, -, *, /)이나 .abs(), .floor(), .ceiling() 같은 수치 메서드를 적용하면 PostgreSQL은 해당 항목이 반드시 numeric 타입이어야 한다고 판단하며, 문자열이 들어오면 즉시 22036 에러를 던집니다. 외부 API나 레거시 시스템에서 숫자를 문자열로 직렬화해 전송하는 경우가 많아 실무에서 자주 발생하는 패턴입니다.

2. JSON 배열 또는 객체에 직접 숫자 함수 적용

JSON Path 표현식에서 배열([]) 또는 객체({}) 타입의 노드에 .abs(), .ceiling() 같은 수치 함수를 적용하려 할 때 발생합니다. 배열 전체를 하나의 값으로 취급하여 숫자 연산을 시도하면 PostgreSQL은 해당 노드가 numeric이 아님을 인식하고 에러를 반환합니다. JSON 구조가 중첩되어 있거나 배열로 래핑되어 있는 경우 경로 표현식을 충분히 펼치지(unwrap) 않아서 발생하는 실수가 많습니다.

3. NULL이나 boolean 값에 대한 수치 연산 시도

JSON 데이터 내의 null, true, false 같은 비수치 리터럴에 대해 산술 연산을 수행하려 할 때도 동일한 에러가 발생합니다. 특히 조건 분기 없이 모든 레코드에 동일한 JSON Path 수치 연산을 일괄 적용하는 경우, 일부 레코드의 해당 필드가 null이거나 true/false인 상황을 사전에 필터링하지 않으면 배치 작업 중간에 에러가 발생하여 전체 트랜잭션이 롤백될 수 있습니다.


해결 방법

원인 1 해결: 문자열을 숫자로 변환 후 연산

JSON Path 내에서 .double() 변환 메서드를 활용하거나, SQL 레벨에서 캐스팅을 명시적으로 처리합니다.

-- 문제 상황: "price" 필드가 문자열 "123.45"로 저장된 경우
SELECT jsonb_path_query('{"price": "123.45"}', '$.price.double()');
-- 결과: 123.45 (정상 동작)

-- 또는 SQL에서 직접 캐스팅
SELECT (data->>'price')::numeric AS price
FROM products
WHERE data->>'price' IS NOT NULL;

-- JSON Path에서 type() 메서드로 타입 먼저 확인
SELECT jsonb_path_query('{"price": "123.45"}', '$.price.type()');
-- 결과: "string"

-- 조건부로 숫자만 필터링 후 연산
SELECT jsonb_path_query_array(
    '{"price": "123.45"}',
    '$.price ? (@ like_regex "^[0-9]+(\\.[0-9]+)?$")'
);

원인 2 해결: 배열/객체 언래핑 후 수치 함수 적용

배열 내 각 항목에 접근하기 위해 [*] 와일드카드를 사용하여 언래핑합니다.

-- 문제 상황: 배열에 직접 .abs() 적용
-- SELECT jsonb_path_query('{"scores": [10, -20, 30]}', '$.scores.abs()');
-- ERROR: 22036

-- 해결: 배열 언래핑 후 수치 함수 적용
SELECT jsonb_path_query('{"scores": [10, -20, 30]}', '$.scores[*].abs()');
-- 결과: 10, 20, 30

-- 실무 예시: 주문 데이터에서 음수 금액 절댓값 추출
SELECT
    id,
    jsonb_path_query_array(order_data, '$.items[*].amount.abs()') AS abs_amounts
FROM orders
WHERE jsonb_path_exists(order_data, '$.items[*]');

원인 3 해결: NULL 및 boolean 필터링 선행

수치 연산 전에 반드시 해당 필드가 숫자 타입인지 검사합니다.

-- 문제 상황: null 또는 boolean 포함 데이터에 일괄 수치 연산
-- SELECT jsonb_path_query('{"value": null}', '$.value.abs()');
-- ERROR: 22036

-- 해결 방법 1: is unknown / 타입 체크 조건 추가
SELECT jsonb_path_query(
    '{"value": 42}',
    '$.value ? (@ != null && @ >= 0)'
);

-- 해결 방법 2: SQL WHERE 절에서 사전 필터링
SELECT
    id,
    jsonb_path_query(data, '$.value.abs()') AS abs_value
FROM sensor_data
WHERE jsonb_typeof(data->'value') = 'number';

-- 해결 방법 3: try_ 접두사 함수 사용 (에러 무시, NULL 반환)
SELECT jsonb_path_query_first(
    '{"value": true}',
    'strict $.value.abs()'
) AS result;

-- jsonb_path_query 에러 무시하고 싶을 때: lax 모드 활용
SELECT jsonb_path_query_first(
    data,
    'lax $.score.floor()'
) AS floor_score
FROM game_results;

예방 방법

1. JSON 데이터 입력 시 CHECK 제약 조건 및 타입 검증 적용

데이터가 테이블에 삽입되는 시점에 JSON 필드의 중요한 숫자 항목이 실제로 numeric 타입인지 검증하는 CHECK 제약 조건을 추가합니다. 이를 통해 잘못된 타입의 데이터가 DB에 저장되는 것을 원천 차단할 수 있습니다.

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

-- 트리거를 활용한 더 세밀한 검증
CREATE OR REPLACE FUNCTION validate_product_json()
RETURNS TRIGGER AS $$
BEGIN
    IF jsonb_typeof(NEW.data->'price') NOT IN ('number', 'null') THEN
        RAISE EXCEPTION 'price 필드는 숫자여야 합니다. 현재 타입: %',
            jsonb_typeof(NEW.data->'price');
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

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

2. JSON Path 표현식 실행 전 lax 모드 또는 타입 확인 래퍼 함수 표준화

팀 내에서 JSON Path 수치 연산을 수행할 때는 항상 lax 모드를 사용하거나, 공통 래퍼 함수를 만들어 타입 검증을 내재화하는 코딩 표준을 수립합니다. 이를 통해 개별 쿼리 작성자가 매번 타입 체크를 신경 쓰지 않아도 안전하게 동작하는 환경을 만들 수 있습니다.

-- 안전한 숫자 추출 래퍼 함수 생성
CREATE OR REPLACE FUNCTION safe_json_numeric(
    p_data jsonb,
    p_path text,
    p_default numeric DEFAULT NULL
)
RETURNS numeric AS $$
DECLARE
    v_result jsonb;
BEGIN
    v_result := jsonb_path_query_first(p_data, p_path::jsonpath);
    IF jsonb_typeof(v_result) = 'number' THEN
        RETURN (v_result)::numeric;
    ELSE
        RETURN p_default;
    END IF;
EXCEPTION WHEN OTHERS THEN
    RETURN p_default;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 사용 예시
SELECT safe_json_numeric(data, '$.price', 0) AS safe_price
FROM products;

관련 에러

  • 22033 (invalid sql json subscript): JSON 배열 인덱스가 잘못된 타입(숫자가 아닌 값)으로 지정되었을 때 발생하며, 배열 접근 로직 오류 시 함께 나타나는 경우가 많습니다.
  • 22034 (non numeric sql json item) 계열의 22035 (non unique keys in a json object): JSON 객체 처리 중 키 중복 관련 에러로, JSON 데이터 품질 문제 시 22036과 함께 발생할 수 있습니다.
  • 22023 (invalid_parameter_value): jsonb_typeof() 등의 함수에 잘못된 인자를 전달할 때 발생하며, JSON 타입 검증 로직 구현 중 함께 마주칠 수 있습니다.
  • 42883 (undefined_function): JSON Path 표현식에서 존재하지 않는 메서드를 호출할 때 발생하며, 수치 메서드 오타 시 22036 대신 이 에러가 나타날 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기