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

22036
2026년 10월 26일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22036 non numeric sql json item 는?

PostgreSQL 에러 코드 22036은 non numeric sql json item 에러로, SQL/JSON 경로 표현식(JSON Path Expression)을 사용할 때 숫자형 연산이나 함수에 숫자가 아닌 JSON 값을 전달했을 때 발생합니다. 예를 들어, jsonb_path_query 또는 @@ 연산자를 사용하여 JSON 데이터를 쿼리하는 과정에서 산술 연산이나 수치 비교를 수행할 대상 값이 문자열, 불리언, 배열, 또는 객체일 경우 이 에러가 트리거됩니다. 이 에러는 PostgreSQL 12 버전에서 SQL/JSON Path 기능이 공식 도입된 이후 JSON 데이터를 집중적으로 다루는 환경에서 종종 마주치게 되는 에러입니다.


주요 발생 원인

1. JSON Path 산술 연산에 비숫자 값 사용

SQL/JSON Path 표현식 내에서 덧셈(+), 뺄셈(-), 곱셈(*), 나눗셈(/) 같은 산술 연산을 수행할 때, 해당 JSON 항목이 문자열이나 다른 비숫자 타입이면 에러가 발생합니다. 예를 들어 JSON 필드의 값이 "123" (문자열)이거나 true (불리언)인 경우, PostgreSQL의 JSON Path 엔진은 이를 숫자로 자동 변환하지 않고 즉시 에러를 반환합니다. 실무에서는 외부 API로부터 수신한 JSON 데이터가 의도치 않게 숫자 값을 문자열로 인코딩하는 경우가 매우 흔하므로 특히 주의가 필요합니다.

2. JSON Path 수치 비교 함수에서 비숫자 입력

abs(), floor(), ceiling() 같은 JSON Path 내장 수치 함수에 숫자가 아닌 JSON 값을 입력으로 전달할 때 이 에러가 발생합니다. 이 함수들은 오직 숫자 타입의 JSON 항목만 처리할 수 있으며, 그 외의 타입이 입력될 경우 엄격하게 에러를 발생시킵니다. JSON 컬럼에 데이터를 저장할 때 타입 일관성이 보장되지 않는 환경(예: NoSQL에서 마이그레이션한 데이터)에서 특히 자주 발생합니다.

3. strict 모드에서의 타입 불일치

JSON Path 표현식을 strict 모드로 실행할 때, 숫자를 기대하는 연산에서 타입이 다른 JSON 항목을 만나면 바로 에러를 던집니다. lax 모드에서는 일부 타입 불일치를 조용히 무시하거나 빈 시퀀스를 반환하지만, strict 모드는 이를 허용하지 않습니다. 운영 환경에서 데이터 정합성 검증 목적으로 strict 모드를 사용하다가 예상치 못한 타입의 데이터를 만나 에러가 발생하는 경우가 많습니다.


해결 방법

원인 1 해결: 산술 연산 전 타입 확인 및 변환

JSON Path 내에서 직접 타입을 필터링하거나, SQL 레벨에서 캐스팅을 수행합니다.

-- 문제가 되는 쿼리 예시
SELECT jsonb_path_query('{"price": "150"}', '$.price + 10');
-- ERROR: 22036: non numeric sql json item

-- 해결책 1: SQL 레벨에서 명시적 캐스팅
SELECT (jsonb_path_query('{"price": "150"}', '$.price') #>> '{}')::numeric + 10;

-- 해결책 2: JSON Path 내에서 is unknown 필터로 숫자 타입만 처리
SELECT jsonb_path_query(
    '[{"price": "150"}, {"price": 200}, {"price": true}]',
    '$[*].price ? (@ is unknown == false)'
);

-- 해결책 3: jsonb_path_query_array로 결과를 배열로 수집 후 SQL에서 처리
SELECT elem::numeric + 10
FROM jsonb_path_query_array(
    '[{"price": 100}, {"price": 200}]',
    '$[*].price'
) AS arr,
jsonb_array_elements(arr) AS elem
WHERE jsonb_typeof(elem) = 'number';

원인 2 해결: 수치 함수 사용 전 타입 검증

-- 문제가 되는 쿼리 예시
SELECT jsonb_path_query('{"score": "high"}', 'abs($.score)');
-- ERROR: 22036: non numeric sql json item

-- 해결책: 타입 필터 적용 후 수치 함수 사용
SELECT jsonb_path_query(
    '{"score": -95.5}',
    'abs($.score)'
);
-- 결과: 95.5

-- 혼합 데이터에서 숫자 타입만 필터링하여 abs() 적용
SELECT jsonb_path_query(
    '[{"val": -10}, {"val": "text"}, {"val": 3.5}]',
    '$[*].val ? (@ > -99999 && @ < 99999)'  -- 숫자만 통과하는 범위 필터
);

-- 더 명시적인 방법: SQL WHERE 절로 타입 보호
SELECT jsonb_path_query(data, 'abs($.val)')
FROM (
    SELECT '{"val": -42}'::jsonb AS data
    WHERE jsonb_typeof('{"val": -42}'::jsonb -> 'val') = 'number'
) sub;

원인 3 해결: strict 모드와 lax 모드 선택적 사용

-- strict 모드에서 에러 발생
SELECT jsonb_path_query('{"amount": "not_a_number"}', 'strict $.amount + 1');
-- ERROR: 22036: non numeric sql json item

-- lax 모드로 변경하여 에러 회피 (단, 데이터 정합성은 별도 확인 필요)
SELECT jsonb_path_query('{"amount": "not_a_number"}', 'lax $.amount + 1');
-- 결과: 빈 결과 반환 (에러 없음)

-- jsonb_path_query_first로 NULL 반환 활용 (에러 없이 안전하게 처리)
SELECT jsonb_path_query_first(
    '{"amount": "not_a_number"}',
    'lax $.amount'
);

-- jsonb_path_exists로 사전 검증 후 처리하는 패턴
DO $$
DECLARE
    v_data jsonb := '{"amount": 500}';
BEGIN
    IF jsonb_path_exists(v_data, '$.amount ? (@ > 0)') THEN
        RAISE NOTICE '처리 가능한 숫자 값: %',
            jsonb_path_query_first(v_data, '$.amount');
    ELSE
        RAISE NOTICE '숫자가 아닌 값이 감지되었습니다. 데이터를 확인하세요.';
    END IF;
END $$;

-- 실무에서 많이 사용하는 안전한 래퍼 함수 생성
CREATE OR REPLACE FUNCTION safe_json_numeric(
    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 jsonb_typeof(v_result) != 'number' THEN
        RETURN NULL;
    END IF;
    RETURN (v_result #>> '{}')::numeric;
EXCEPTION WHEN OTHERS THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;

-- 사용 예시
SELECT safe_json_numeric('{"price": 99.9}', '$.price');   -- 99.9
SELECT safe_json_numeric('{"price": "abc"}', '$.price');  -- NULL

예방 방법

1. JSON 데이터 저장 시 입력 유효성 검사 강화

JSON 컬럼에 데이터를 INSERT 또는 UPDATE 할 때, CHECK 제약 조건이나 트리거를 활용하여 숫자 타입이어야 하는 필드의 타입을 사전에 강제하는 것이 최선입니다. 이렇게 하면 데이터가 DB에 들어오는 시점에 타입 일관성을 보장할 수 있습니다.

-- CHECK 제약 조건으로 특정 JSON 필드의 타입 강제
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    data jsonb NOT NULL,
    CONSTRAINT chk_price_is_numeric
        CHECK (jsonb_typeof(data -> 'price') = 'number'),
    CONSTRAINT chk_quantity_is_numeric
        CHECK (jsonb_typeof(data -> 'quantity') = 'number')
);

-- 올바른 삽입
INSERT INTO orders (data) VALUES ('{"price": 29.99, "quantity": 3}');

-- 에러 유발 삽입 (차단됨)
INSERT INTO orders (data) VALUES ('{"price": "cheap", "quantity": 3}');
-- ERROR: new row violates check constraint "chk_price_is_numeric"

2. 애플리케이션 계층에서 JSON 스키마 검증 도입

PostgreSQL에 데이터가 들어오기 전, 애플리케이션 레벨에서 JSON Schema 검증을 수행하거나, PostgreSQL 확장(pg_jsonschema 등)을 활용하여 JSON 구조와 타입을 강제하는 것을 강력히 권장합니다. 특히 외부 API, 메시지 큐, ETL 파이프라인으로부터 유입되는 JSON 데이터는 항상 신뢰할 수 없으므로, 타입 검증 레이어를 반드시 둬야 합니다.

-- pg_jsonschema 확장 활용 예시 (PostgreSQL 14+)
CREATE EXTENSION IF NOT EXISTS pg_jsonschema;

CREATE TABLE product_events (
    id SERIAL PRIMARY KEY,
    payload jsonb NOT NULL,
    CONSTRAINT validate_payload_schema CHECK (
        jsonb_matches_schema(
            '{
                "type": "object",
                "properties": {
                    "price":    {"type": "number"},
                    "quantity": {"type": "integer"},
                    "name":     {"type": "string"}
                },
                "required": ["price", "quantity"]
            }',
            payload
        )
    )
);

관련 에러

  • 22033 (invalid SQL JSON subscript): JSON Path에서 잘못된 배열 인덱스를 사용할 때 발생하며, 22036과 마찬가지로 JSON Path 타입 관련 에러 패밀리에 속합니다.
  • 22034 (more than one SQL JSON item): jsonb_path_query_first 대신 단일 값을 기대하는 컨텍스트에서 여러 JSON 항목이 반환될 때 발생합니다.
  • 22035 (no SQL JSON item): strict 모드에서 JSON Path가 존재하지 않는 경로를 참조할 때 발생하며, 22036과 함께 JSON Path strict 모드 사용 시 자주 동반 발생합니다.
  • 22032 (invalid input syntax for type json): JSON 파싱 자체가 실패할 때 발생하며, 22036보다 앞선 단계에서 트리거됩니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기