2026년 08월 21일 | DBMS Error 가이드
이 글에서 다루는 내용
22033 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22033 invalid sql json subscript 는?
PostgreSQL 에러 코드 22033 (invalid_sql_json_subscript) 은 SQL/JSON 경로 표현식(JSON Path Expression)에서 배열 또는 객체에 접근할 때 유효하지 않은 서브스크립트(subscript) 를 사용했을 때 발생하는 에러입니다. 주로 jsonb_path_query, jsonb_path_exists, JSON_QUERY, JSON_VALUE 등 SQL/JSON 함수를 사용할 때, 배열 인덱스나 객체 키가 JSON 데이터 구조와 맞지 않는 경우에 나타납니다. PostgreSQL 12 버전 이후 JSON Path 기능이 강화되면서 이 에러를 접할 기회가 많아졌으며, 특히 동적으로 생성된 JSON 경로를 사용하는 애플리케이션에서 자주 발생합니다.
주요 발생 원인
1. 배열이 아닌 JSON 값에 배열 인덱스 접근 시도
가장 흔한 원인으로, JSON 데이터 내 특정 키가 배열이 아닌 객체(Object) 또는 스칼라 값(숫자, 문자열, 불리언 등)임에도 불구하고 배열 인덱스([0], [1] 등)로 접근을 시도할 때 발생합니다. 예를 들어 {"name": "Alice"} 와 같은 JSON 객체에서 $.name[0] 처럼 접근하면 name 필드는 문자열이므로 배열 인덱스를 사용할 수 없어 에러가 발생합니다. 데이터 스키마가 명확하지 않거나, 외부 시스템에서 수신한 JSON 데이터의 구조가 예상과 다를 때 특히 자주 발생합니다.
2. 음수 또는 범위를 벗어난 배열 인덱스 사용
JSON Path에서 배열 인덱스는 0부터 시작하며, 배열의 길이를 초과하거나 PostgreSQL의 SQL/JSON 표준에서 허용되지 않는 음수 인덱스를 사용할 경우 이 에러가 발생할 수 있습니다. 예를 들어 길이가 3인 배열 [10, 20, 30] 에 $[5] 또는 $[-1] 처럼 접근하면 서브스크립트가 유효하지 않다고 판단하여 에러를 반환합니다. 실제 데이터와 경로 표현식 사이의 인덱스 불일치는 배치 처리나 대량 데이터 마이그레이션 시 특히 문제가 됩니다.
3. 객체(Object)에 정수형 키로 접근 시도
JSON 객체는 문자열 키로 접근해야 하는데, 정수형 서브스크립트를 통해 객체 필드에 접근하려 할 때도 이 에러가 발생합니다. {"0": "zero", "1": "one"} 처럼 숫자처럼 보이는 문자열 키를 가진 객체에서도, JSON Path에서 $[0]이라는 배열 인덱스 표기는 배열에만 유효하므로, 객체에 적용 시 에러가 발생합니다. ORM(Object-Relational Mapping) 프레임워크나 자동 생성된 쿼리에서 이런 실수가 발생하기 쉽습니다.
해결 방법
원인 1 해결: 타입 확인 후 안전하게 접근하기
배열인지 확인한 후에 인덱스로 접근하거나, jsonb_typeof 함수로 타입을 먼저 검사하세요.
-- 문제가 되는 쿼리 예시
SELECT jsonb_path_query('{"name": "Alice"}'::jsonb, '$.name[0]');
-- ERROR: 22033: invalid sql json subscript
-- 해결 방법 1: jsonb_typeof로 타입 확인 후 분기 처리
SELECT
CASE
WHEN jsonb_typeof(data->'name') = 'array' THEN (data->'name')->0
ELSE data->'name'
END AS name_value
FROM (SELECT '{"name": "Alice"}'::jsonb AS data) t;
-- 해결 방법 2: jsonb_path_query_first 와 lax 모드 활용
-- lax 모드에서는 타입 불일치 시 에러 대신 null 반환
SELECT jsonb_path_query_first(
'{"name": "Alice"}'::jsonb,
'lax $.name[0]'
);
-- lax 모드: null 반환 (에러 없음)
-- 해결 방법 3: JSON_QUERY에서 lax 모드 사용 (PostgreSQL 16+)
SELECT JSON_QUERY(
'{"name": "Alice"}',
'lax $.name[0]'
NULL ON ERROR
);
원인 2 해결: 배열 길이 검증 후 인덱스 접근
-- 문제가 되는 쿼리 예시
SELECT jsonb_path_query('[10, 20, 30]'::jsonb, '$[5]');
-- ERROR: 22033: invalid sql json subscript
-- 해결 방법 1: jsonb_array_length로 길이 확인 후 접근
DO $$
DECLARE
v_json jsonb := '[10, 20, 30]';
v_index int := 5;
BEGIN
IF v_index < jsonb_array_length(v_json) THEN
RAISE NOTICE 'Value: %', v_json->v_index;
ELSE
RAISE NOTICE 'Index % is out of bounds (array length: %)',
v_index, jsonb_array_length(v_json);
END IF;
END;
$$;
-- 해결 방법 2: lax 모드로 범위 초과 시 null 반환
SELECT jsonb_path_query_first(
'[10, 20, 30]'::jsonb,
'lax $[5]'
);
-- 결과: null (에러 없음)
-- 해결 방법 3: 배열의 모든 요소를 안전하게 순회
SELECT idx, value
FROM jsonb_array_elements('[10, 20, 30]'::jsonb) WITH ORDINALITY AS t(value, idx)
WHERE idx <= 3;
원인 3 해결: 객체는 문자열 키로 접근
-- 문제가 되는 쿼리 예시
SELECT jsonb_path_query('{"0": "zero", "1": "one"}'::jsonb, '$[0]');
-- ERROR: 22033: invalid sql json subscript
-- 해결 방법 1: 문자열 키 표기법 사용
SELECT jsonb_path_query(
'{"0": "zero", "1": "one"}'::jsonb,
'$.\"0\"'
);
-- 결과: "zero"
-- 해결 방법 2: 직접 연산자 사용 (->> 또는 ->)
SELECT '{"0": "zero", "1": "one"}'::jsonb ->> '0';
-- 결과: zero
-- 해결 방법 3: 데이터 타입 사전 확인 함수
CREATE OR REPLACE FUNCTION safe_json_get(
p_data jsonb,
p_key text
) RETURNS jsonb AS $$
BEGIN
IF jsonb_typeof(p_data) = 'object' THEN
RETURN p_data -> p_key;
ELSIF jsonb_typeof(p_data) = 'array' THEN
RETURN p_data -> p_key::int;
ELSE
RETURN NULL;
END IF;
EXCEPTION WHEN OTHERS THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;
-- 사용 예시
SELECT safe_json_get('{"0": "zero"}'::jsonb, '0');
SELECT safe_json_get('[10, 20, 30]'::jsonb, '1');
예방 방법
1. SQL/JSON Path 에서 항상 lax 모드 기본으로 사용하기
운영 환경에서는 JSON 데이터의 구조가 예상과 다를 수 있으므로, JSON Path 표현식에서 기본적으로 lax 모드를 사용하는 것을 권장합니다. lax 모드는 타입 불일치나 범위 초과 시 에러를 발생시키지 않고 null을 반환하기 때문에, 애플리케이션의 안정성을 높일 수 있습니다. 반드시 엄격한 검증이 필요한 ETL 파이프라인이나 데이터 정합성 체크 쿼리에서만 strict 모드를 선택적으로 사용하세요.
-- lax 모드를 기본으로 사용하는 예시 (COALESCE와 함께 기본값 처리)
SELECT COALESCE(
jsonb_path_query_first(data, 'lax $.items[0].price'),
'0'::jsonb
) AS first_item_price
FROM orders;
2. JSON 데이터 입력 시점에 스키마 유효성 검사 적용
데이터가 테이블에 저장될 때부터 JSON 구조를 강제하는 CHECK 제약 조건이나 트리거를 설정하면, 이후 쿼리에서 발생할 수 있는 서브스크립트 에러를 사전에 방지할 수 있습니다. PostgreSQL의 jsonb_path_exists 함수를 활용하면 필수 필드의 존재 여부나 타입을 삽입/수정 시점에 검증할 수 있습니다.
-- JSON 스키마 유효성 검사 CHECK 제약 조건 예시
CREATE TABLE orders (
id serial PRIMARY KEY,
data jsonb NOT NULL,
-- items 필드가 반드시 배열이어야 하고, price는 숫자여야 함
CONSTRAINT chk_orders_json_structure CHECK (
jsonb_typeof(data->'items') = 'array'
AND jsonb_path_exists(data, '$.items[*].price ? (@ > 0)')
)
);
-- 잘못된 데이터 삽입 시 에러 발생 (사전 차단)
INSERT INTO orders (data) VALUES ('{"items": "not_an_array"}');
-- ERROR: new row for relation "orders" violates check constraint
관련 에러
- 22032 (invalid_json_text): JSON 문자열 자체가 파싱 불가능한 형식일 때 발생하며, 22033 이전 단계에서 나타날 수 있습니다.
- 22034 (sql_json_array_not_found): JSON Path 평가 결과 배열을 기대했으나 배열이 반환되지 않을 때 발생하며, 22033과 함께 JSON 배열 처리 시 자주 쌍으로 등장합니다.
- 22035 (sql_json_member_not_found): JSON 객체에서 존재하지 않는 키를 strict 모드로 접근할 때 발생합니다. 22033과 함께 JSON Path 관련 에러 그룹(2203x 계열)을 이루며, 모두 lax 모드 전환으로 완화할 수 있습니다.
- 22023 (invalid_parameter_value): JSON 함수에 잘못된 파라미터가 전달될 때 발생하며, 서브스크립트 에러와 혼동될 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.