2026년 10월 25일 | DBMS Error 가이드
이 글에서 다루는 내용
22033 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22033 invalid sql json subscript 는?
PostgreSQL 에러 코드 22033 (invalid sql json subscript) 는 JSON 또는 JSONB 데이터를 다룰 때 SQL/JSON 경로 표현식(path expression)이나 서브스크립트(subscript) 연산자를 잘못 사용했을 때 발생하는 에러입니다. 특히 PostgreSQL 14 버전부터 도입된 JSON 서브스크립트 문법(jsonb_column['key'] 또는 jsonb_column[index])을 사용할 때, 잘못된 키 타입이나 범위를 벗어난 인덱스, 또는 SQL/JSON 경로 함수(jsonb_path_query, jsonb_path_exists 등)에서 잘못된 경로를 지정하면 이 에러가 발생합니다. 이 에러는 데이터 타입 불일치, 잘못된 경로 표현식, 또는 JSON 배열 인덱스 처리 방식의 오해에서 비롯되는 경우가 많습니다.
주요 발생 원인
1. 잘못된 JSON 배열 서브스크립트 타입 사용
가장 흔한 원인으로, JSON 배열에 접근할 때 정수(integer)가 아닌 문자열이나 실수(float) 값을 인덱스로 사용하는 경우입니다. PostgreSQL의 SQL/JSON 표준에서는 배열 인덱스는 반드시 정수 타입이어야 하며, 문자열 형태의 숫자('0', '1')나 소수(0.5)를 서브스크립트로 사용하면 이 에러가 발생합니다. 예를 들어 동적 쿼리를 생성하는 애플리케이션 레이어에서 인덱스 값을 충분히 검증하지 않고 그대로 SQL에 삽입하는 경우 실무에서 자주 마주치는 상황입니다.
2. SQL/JSON 경로 표현식에서의 잘못된 필터 또는 경로 구문
jsonb_path_query(), jsonb_path_exists(), jsonb_path_match() 등의 SQL/JSON 경로 함수를 사용할 때, 경로 표현식 내에서 잘못된 서브스크립트 표현을 사용하면 에러가 발생합니다. 예를 들어 $.items[] 형태로 와일드카드 배열 접근을 해야 할 때 $.items['']처럼 문자열로 감싸거나, 필터 표현식(?(...)) 내부에서 잘못된 비교 연산을 수행하는 경우가 이에 해당합니다. SQL/JSON 경로 문법은 일반 SQL 문법과 다르기 때문에, 처음 사용하는 개발자나 DBA가 혼동하기 쉬운 영역입니다.
3. JSONB 서브스크립트 UPDATE 구문에서의 오용
PostgreSQL 14부터 지원되는 UPDATE 문에서의 JSONB 서브스크립트 할당 문법(UPDATE t SET col['key'] = 'value')을 잘못 사용하는 경우입니다. 중첩된 경로를 지정할 때 정수 인덱스 대신 부적절한 타입을 사용하거나, 존재하지 않는 깊은 경로에 값을 할당하려 할 때 이 에러가 발생할 수 있습니다. 특히 배열 내 특정 인덱스를 업데이트할 때 변수를 바인딩하는 방식이 잘못된 경우 실무에서 종종 발생합니다.
해결 방법
원인 1 해결: 배열 서브스크립트 타입 교정
배열 인덱스는 반드시 정수 리터럴 또는 정수로 캐스팅된 값을 사용해야 합니다.
-- 에러 발생 예시: 문자열 인덱스 사용
SELECT ('["apple", "banana", "cherry"]'::jsonb)['1'];
-- ERROR: 22033 invalid sql json subscript
-- 올바른 방법: 정수 인덱스 사용
SELECT ('["apple", "banana", "cherry"]'::jsonb)[1];
-- 결과: "banana"
-- 동적 인덱스를 사용하는 경우 명시적 캐스팅
DO $$
DECLARE
v_index text := '2';
BEGIN
-- 잘못된 방법 (에러 발생 가능)
-- PERFORM ('["a","b","c"]'::jsonb)[v_index];
-- 올바른 방법: integer로 명시적 캐스팅
PERFORM ('["a","b","c"]'::jsonb)[v_index::integer];
RAISE NOTICE '값: %', ('["a","b","c"]'::jsonb)[v_index::integer];
END;
$$;
-- 테이블 컬럼에서 배열 요소 접근
CREATE TABLE IF NOT EXISTS product_data (
id SERIAL PRIMARY KEY,
tags JSONB
);
INSERT INTO product_data (tags) VALUES ('["electronics", "mobile", "5G"]');
-- 올바른 정수 인덱스 접근
SELECT id, tags[0] AS first_tag, tags[1] AS second_tag
FROM product_data;
원인 2 해결: SQL/JSON 경로 표현식 교정
경로 함수에서는 SQL/JSON Path 언어 문법을 정확히 따라야 합니다.
-- 테스트용 데이터 준비
CREATE TABLE IF NOT EXISTS orders (
id SERIAL PRIMARY KEY,
order_data JSONB
);
INSERT INTO orders (order_data) VALUES
('{"items": [{"name": "keyboard", "price": 50000}, {"name": "mouse", "price": 30000}]}'),
('{"items": [{"name": "monitor", "price": 300000}]}');
-- 에러 발생 예시: 잘못된 배열 와일드카드 표현
SELECT jsonb_path_query(order_data, '$.items["*"].name')
FROM orders;
-- ERROR: 22033 invalid sql json subscript
-- 올바른 방법: 와일드카드는 따옴표 없이 사용
SELECT jsonb_path_query(order_data, '$.items[*].name')
FROM orders;
-- 필터 표현식을 올바르게 사용하는 예
SELECT jsonb_path_query(
order_data,
'$.items[*] ? (@.price > 40000).name'
)
FROM orders;
-- jsonb_path_exists 올바른 사용
SELECT id, jsonb_path_exists(order_data, '$.items[0]') AS has_first_item
FROM orders;
-- 특정 인덱스 범위 접근
SELECT jsonb_path_query(order_data, '$.items[0 to 1].name')
FROM orders;
-- last 키워드 사용 (마지막 요소)
SELECT jsonb_path_query(order_data, '$.items[last].name')
FROM orders;
원인 3 해결: JSONB 서브스크립트 UPDATE 교정
UPDATE 문에서 JSONB 서브스크립트를 올바르게 사용하려면 경로를 명확히 지정해야 합니다.
-- 테스트 테이블 생성
CREATE TABLE IF NOT EXISTS user_profiles (
id SERIAL PRIMARY KEY,
profile JSONB
);
INSERT INTO user_profiles (profile) VALUES
('{"name": "홍길동", "scores": [85, 90, 78], "address": {"city": "서울"}}');
-- 에러 발생 예시: 잘못된 서브스크립트 타입으로 배열 업데이트
-- UPDATE user_profiles SET profile['scores']['1.0'] = '95';
-- ERROR: 22033 invalid sql json subscript
-- 올바른 방법 1: 정수 인덱스로 배열 요소 업데이트
UPDATE user_profiles
SET profile['scores'][1] = '95'
WHERE id = 1;
SELECT profile FROM user_profiles WHERE id = 1;
-- {"name": "홍길동", "scores": [85, 95, 78], "address": {"city": "서울"}}
-- 올바른 방법 2: 중첩 객체 키 업데이트
UPDATE user_profiles
SET profile['address']['city'] = '"부산"'
WHERE id = 1;
SELECT profile FROM user_profiles WHERE id = 1;
-- 올바른 방법 3: jsonb_set 함수 사용 (더 안전한 방법)
UPDATE user_profiles
SET profile = jsonb_set(profile, '{scores, 2}', '100')
WHERE id = 1;
-- 배열 전체를 안전하게 교체
UPDATE user_profiles
SET profile = jsonb_set(
profile,
'{scores}',
(profile->'scores') || '[99]'::jsonb
)
WHERE id = 1;
예방 방법
1. 입력값 검증 및 타입 안전 함수 사용
애플리케이션 레이어에서 JSON 경로에 사용될 인덱스나 키 값을 SQL에 삽입하기 전에 반드시 유효성을 검증하는 로직을 구현하세요. 특히 배열 인덱스는 IS NOT NULL과 정수 범위 체크를 선행하고, 가능하면 동적 서브스크립트 대신 jsonb_set(), jsonb_extract_path(), jsonb_extract_path_text() 같은 함수를 사용하는 것이 더 안전합니다.
-- 안전한 배열 접근을 위한 래퍼 함수 예시
CREATE OR REPLACE FUNCTION safe_json_array_get(
p_data JSONB,
p_index INTEGER
) RETURNS JSONB AS $$
BEGIN
-- 인덱스 유효성 검증
IF p_index IS NULL OR p_index < 0 THEN
RAISE WARNING '유효하지 않은 인덱스: %', p_index;
RETURN NULL;
END IF;
IF jsonb_array_length(p_data) <= p_index THEN
RAISE WARNING '인덱스 범위 초과: 배열 길이=%, 요청 인덱스=%',
jsonb_array_length(p_data), p_index;
RETURN NULL;
END IF;
RETURN p_data[p_index];
END;
$$ LANGUAGE plpgsql;
-- 사용 예시
SELECT safe_json_array_get('["a","b","c"]'::jsonb, 1); -- "b"
SELECT safe_json_array_get('["a","b","c"]'::jsonb, 10); -- NULL (경고 출력)
2. SQL/JSON 경로 표현식 사전 검증
운영 환경에 배포하기 전에 jsonb_path_query_array() 또는 jsonb_path_exists()를 활용하여 경로 표현식을 개발/스테이징 환경에서 충분히 테스트하고, 가능하면 jsonb_path_query 계열 함수에 vars 파라미터를 활용하여 동적 값을 경로 표현식 내에 안전하게 주입하세요.
-- vars 파라미터를 이용한 안전한 동적 경로 쿼리
SELECT jsonb_path_query(
'{"items": [{"id": 1, "price": 500}, {"id": 2, "price": 1500}]}'::jsonb,
'$.items[*] ? (@.price > $min_price)',
'{"min_price": 1000}'::jsonb -- vars로 동적 값 전달
);
-- 결과: {"id": 2, "price": 1500}
-- 경로 유효성 테스트 쿼리
SELECT jsonb_path_exists(
'{"data": [1,2,3]}'::jsonb,
'$.data[*]'
) AS path_valid;
관련 에러
- 22032 (invalid_json_text): JSON 문자열 자체가 잘못된 형식일 때 발생.
22033이 경로/서브스크립트 오류라면,22032는 JSON 파싱 자체의 실패입니다. - 22034 (more_json_than_one_result): SQL/JSON 경로 표현식이 단일 결과를 기대하는 컨텍스트에서 여러 값을 반환할 때 발생.
- 22035 (no_json_item): SQL/JSON 경로 표현식이 결과를 찾지 못했을 때
ERROR모드에서 발생하며, 존재하지 않는 배열 인덱스 접근과 함께 나타날 수 있습니다. - 42883 (undefined_function): JSON 관련 함수를 잘못된 인자 타입과 함께 호출할 때 발생하며,
22033과 함께 디버깅 과정에서 마주칠 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.