2026년 06월 18일 | 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 Path 쿼리에서 산술 연산(+, -, *, /)이나 비교 연산을 수행할 때, 대상 JSON 항목이 문자열, 불리언, 배열, 객체 등 숫자가 아닌 타입이면 이 에러가 발생합니다. 이 에러는 PostgreSQL 12 버전부터 도입된 SQL/JSON Path 기능과 밀접하게 관련되어 있으며, JSON 데이터의 타입 검증이 불충분한 환경에서 자주 나타납니다.
주요 발생 원인
1. JSON Path 산술 연산 대상이 문자열인 경우
JSON 데이터 내 특정 키의 값이 숫자처럼 보이지만 실제로는 문자열("123")로 저장되어 있는 경우, JSON Path에서 산술 연산을 시도하면 22036 에러가 발생합니다. 이는 외부 API로부터 수신한 JSON 데이터나 레거시 시스템에서 마이그레이션된 데이터에서 매우 흔하게 발생하는 상황입니다. 데이터를 저장할 때 타입 강제를 하지 않았기 때문에 나중에 쿼리 단계에서 타입 충돌이 발생하게 됩니다.
2. JSON 배열 또는 객체에 직접 수치 연산을 적용하는 경우
JSON Path 표현식에서 배열([1, 2, 3]) 또는 객체({"a": 1}) 자체에 수치 연산자를 적용하려 할 때 이 에러가 발생합니다. 개발자가 배열 내 개별 요소에 접근하지 않고 배열 전체를 대상으로 연산을 시도하는 실수가 원인인 경우가 많습니다. 특히 JSON 구조가 복잡한 중첩 형태일 때 경로 표현식을 잘못 작성하면 이런 상황이 쉽게 발생합니다.
3. NULL이나 불리언 값이 수치 연산 경로에 포함된 경우
JSON 데이터에 null, true, false 값이 포함된 필드에 대해 JSON Path 산술 연산을 적용하는 경우에도 동일한 에러가 발생합니다. 데이터 수집 과정에서 누락된 값이 null로 처리되거나, 플래그성 필드가 불리언으로 저장되어 있는 상황에서 일괄 수치 연산을 시도하면 에러가 발생합니다. 이 경우는 데이터 품질 관리가 되지 않은 환경에서 특히 자주 나타납니다.
해결 방법
원인 1: 문자열로 저장된 숫자 처리
JSON Path 내에서 .double() 또는 .bigint() 등의 타입 변환 메서드를 사용하거나, SQL 레벨에서 캐스팅을 적용하여 해결합니다.
-- 문제 발생 예시: 값이 문자열 "100"으로 저장된 경우
SELECT jsonb_path_query('{"price": "100"}', '$.price + 50');
-- ERROR: 22036 non numeric sql json item
-- 해결책 1: JSON Path의 .double() 변환 메서드 사용
SELECT jsonb_path_query('{"price": "100"}', '$.price.double() + 50');
-- 결과: 150
-- 해결책 2: SQL 레벨에서 캐스팅 처리
SELECT (data->>'price')::numeric + 50
FROM (SELECT '{"price": "100"}'::jsonb AS data) t;
-- 결과: 150
-- 해결책 3: jsonb_path_query_first로 안전하게 추출 후 캐스팅
SELECT jsonb_path_query_first(
'{"price": "100"}',
'$.price'
)::text::numeric + 50;
-- 결과: 150
원인 2: 배열 또는 객체에 직접 수치 연산 적용
배열의 특정 인덱스 또는 [*] 와일드카드를 사용하여 개별 요소를 명확히 지정한 후 연산합니다.
-- 문제 발생 예시: 배열 전체에 연산 시도
SELECT jsonb_path_query('{"scores": [80, 90, 70]}', '$.scores + 10');
-- ERROR: 22036 non numeric sql json item
-- 해결책 1: 배열 인덱스를 명확히 지정
SELECT jsonb_path_query('{"scores": [80, 90, 70]}', '$.scores[0] + 10');
-- 결과: 90
-- 해결책 2: 와일드카드로 각 요소에 연산 적용
SELECT jsonb_path_query('{"scores": [80, 90, 70]}', '$.scores[*] + 10');
-- 결과: 90, 100, 80 (각각 반환)
-- 해결책 3: 복잡한 집계가 필요한 경우 unnest 활용
SELECT elem::numeric + 10
FROM jsonb_array_elements('{"scores": [80, 90, 70]}'::jsonb->'scores') AS elem;
-- 결과: 90, 100, 80
원인 3: NULL 또는 불리언 값 처리
jsonb_typeof() 함수나 JSON Path의 조건식을 활용하여 타입을 사전에 필터링합니다.
-- 문제 발생 예시: null 값이 포함된 필드에 연산
SELECT jsonb_path_query('{"value": null}', '$.value + 10');
-- ERROR: 22036 non numeric sql json item
-- 해결책 1: jsonb_typeof로 타입 체크 후 처리
SELECT
CASE
WHEN jsonb_typeof(data->'value') = 'number'
THEN (data->>'value')::numeric + 10
ELSE NULL
END AS result
FROM (SELECT '{"value": null}'::jsonb AS data) t;
-- 결과: NULL (에러 없이 처리)
-- 해결책 2: JSON Path 내 타입 필터 사용
SELECT jsonb_path_query(
'{"value": null}',
'$.value ? (@ != null)'
);
-- 결과: 빈 결과 (에러 없이 처리)
-- 해결책 3: 실무에서 사용하는 안전한 래퍼 함수
CREATE OR REPLACE FUNCTION safe_json_numeric(
p_data jsonb,
p_path text,
p_default numeric DEFAULT 0
)
RETURNS numeric
LANGUAGE plpgsql
AS $$
DECLARE
v_result text;
BEGIN
v_result := jsonb_path_query_first(p_data, p_path::jsonpath)::text;
IF v_result IS NULL OR v_result = 'null' THEN
RETURN p_default;
END IF;
RETURN v_result::numeric;
EXCEPTION
WHEN SQLSTATE '22036' THEN
RETURN p_default;
WHEN others THEN
RETURN p_default;
END;
$$;
-- 사용 예시
SELECT safe_json_numeric('{"price": "100"}'::jsonb, '$.price', 0);
-- 결과: 100 (에러 없이 처리)
SELECT safe_json_numeric('{"value": null}'::jsonb, '$.value', -1);
-- 결과: -1 (기본값 반환)
예방 방법
1. JSON 데이터 저장 시 스키마 유효성 검증 제약 조건 적용
JSON 데이터를 테이블에 저장하는 단계부터 CHECK 제약 조건과 jsonb_typeof() 함수를 활용하여 숫자형 필드에는 반드시 숫자 타입만 저장되도록 강제합니다. 이렇게 하면 데이터 입력 단계에서 타입 불일치를 조기에 차단하여 쿼리 실행 시 22036 에러가 발생할 가능성 자체를 원천적으로 줄일 수 있습니다.
-- 숫자형 필드 타입 강제 CHECK 제약 조건 예시
CREATE TABLE product_data (
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 (
data->'quantity' IS NULL OR
jsonb_typeof(data->'quantity') = 'number'
)
);
-- 올바른 데이터 삽입 (성공)
INSERT INTO product_data (data)
VALUES ('{"price": 1000, "quantity": 5}');
-- 잘못된 데이터 삽입 (제약 조건 위반으로 차단)
INSERT INTO product_data (data)
VALUES ('{"price": "1000", "quantity": 5}');
-- ERROR: new row violates check constraint "chk_price_is_numeric"
2. JSON Path 쿼리 실행 전 데이터 타입 사전 검증 루틴 도입
운영 환경에서 JSON Path 쿼리를 사용하는 모든 쿼리에 대해 jsonb_typeof() 또는 jsonb_path_exists()를 활용한 타입 사전 검증 로직을 표준 패턴으로 도입합니다. 특히 외부에서 유입되는 JSON 데이터를 다루는 ETL 파이프라인이나 API 연동 시스템에서는 이 패턴을 필수적으로 적용하여 예상치 못한 타입 오류가 프로덕션 장애로 이어지지 않도록 방어적 프로그래밍을 실천해야 합니다.
-- 타입 검증을 포함한 안전한 JSON Path 쿼리 패턴
SELECT
id,
CASE
WHEN jsonb_typeof(data->'price') = 'number'
AND jsonb_typeof(data->'quantity') = 'number'
THEN (data->>'price')::numeric * (data->>'quantity')::numeric
ELSE NULL
END AS total_amount
FROM product_data;
관련 에러
22033(invalid SQL JSON subscript): JSON Path에서 잘못된 배열 인덱스 구독 시 발생하며, 배열 접근 패턴 오류와 관련됩니다.22034(more than one SQL JSON item):jsonb_path_query_first대신 단일 값 반환을 기대하는 컨텍스트에서 여러 항목이 반환될 때 발생합니다.22035(no SQL JSON item): JSON Path 결과가 없을 때 단일 값을 요구하는 함수에서 발생하며,22036과 함께 JSON Path 관련 에러 그룹을 형성합니다.22032(invalid input syntax for type json): JSON 형식 자체가 잘못된 경우 발생하며, 데이터 입력 단계의 유효성 검증 실패를 나타냅니다.42804(datatype mismatch): JSON 연산 결과를 잘못된 타입의 컬럼에 저장하려 할 때 발생하는 타입 불일치 에러로22036과 함께 자주 나타납니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.