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

22033
2026년 06월 17일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22033 invalid sql json subscript 는?

PostgreSQL 에러 코드 22033 (invalid sql json subscript)은 JSON 또는 JSONB 데이터를 SQL/JSON 경로 표현식(JSON Path)으로 접근할 때, 잘못된 서브스크립트(subscript) 값을 사용한 경우에 발생합니다. 쉽게 말하면, JSON 배열의 인덱스나 JSON 객체의 키를 지정하는 방식이 올바르지 않거나 허용되지 않는 타입·형식을 사용했을 때 이 에러가 트리거됩니다. 특히 PostgreSQL 12 버전 이후 SQL/JSON Path 기능이 강화되면서 jsonb_path_query, @>, ->, jsonb_path_exists 등의 함수와 연산자를 활용하는 환경에서 자주 마주치게 되는 에러입니다.


주요 발생 원인

1. JSON 배열 인덱스에 정수가 아닌 값을 사용한 경우

JSON 배열은 반드시 정수형 인덱스(0부터 시작)로만 접근할 수 있습니다. 그런데 실수로 문자열, 소수, 또는 불리언 값을 인덱스로 사용하면 22033 에러가 발생합니다. 예를 들어 $.items["first"]와 같이 문자열을 배열 인덱스로 사용하거나, $.items[1.5]처럼 소수를 인덱스로 지정하는 것은 SQL/JSON 표준에서 허용되지 않습니다.

2. jsonb_path_query 또는 jsonb_path_exists에서 잘못된 경로 표현식 사용

jsonb_path_query(), jsonb_path_exists() 등의 SQL/JSON 함수에서 경로 표현식(JSON Path Expression)을 작성할 때, 서브스크립트 구문이 명세(specification)와 맞지 않으면 에러가 발생합니다. SQL/JSON 경로에서 배열 슬라이스(slice) 문법을 잘못 사용하거나, 와일드카드([*])와 특정 인덱스를 혼용하는 방식이 잘못된 경우에도 이 에러가 나타납니다. 이는 특히 동적으로 JSON 경로를 생성하는 애플리케이션 코드에서 자주 발생합니다.

3. 배열이 아닌 JSON 오브젝트에 배열 인덱스 접근을 시도한 경우

JSON 오브젝트({} 형태)에 배열처럼 숫자 인덱스로 접근하려 할 때도 이 에러가 발생할 수 있습니다. 예를 들어 '{"name": "홍길동"}'::jsonb와 같은 오브젝트 타입의 데이터에 $[0] 형식으로 접근하면, 해당 값이 배열이 아니기 때문에 서브스크립트 자체가 유효하지 않다고 판단됩니다. 이는 API 응답 데이터의 구조가 환경에 따라 배열이 되기도 하고 오브젝트가 되기도 하는 가변적인 JSON 스키마를 다룰 때 특히 위험합니다.


해결 방법

원인 1 해결: 올바른 정수형 인덱스 사용

배열 접근 시 반드시 0 이상의 정수를 사용하세요. 또한 last 키워드를 활용하면 마지막 요소에 안전하게 접근할 수 있습니다.

-- 잘못된 예시: 문자열 인덱스 사용 (22033 에러 발생)
SELECT jsonb_path_query('["apple", "banana", "cherry"]'::jsonb, '$["first"]');

-- 올바른 예시: 정수 인덱스 사용
SELECT jsonb_path_query('["apple", "banana", "cherry"]'::jsonb, '$[0]');
-- 결과: "apple"

-- last 키워드를 사용하여 마지막 요소 접근
SELECT jsonb_path_query('["apple", "banana", "cherry"]'::jsonb, '$[last]');
-- 결과: "cherry"

-- 슬라이스를 이용한 범위 접근 (올바른 문법)
SELECT jsonb_path_query('["apple", "banana", "cherry", "date"]'::jsonb, '$[0 to 2]');
-- 결과: "apple", "banana", "cherry"

원인 2 해결: jsonb_path_query 경로 표현식 검증 및 수정

jsonb_path_query_arrayjsonb_path_exists를 사용하기 전에 경로 표현식이 올바른지 확인하고, 에러를 안전하게 처리하려면 _tz 또는 _silent 변형 함수를 활용하세요.

-- 잘못된 예시: 소수 인덱스 사용 (22033 에러 발생)
SELECT jsonb_path_query('[1, 2, 3]'::jsonb, '$[1.5]');

-- 올바른 예시: 와일드카드로 모든 배열 요소 접근
SELECT jsonb_path_query('[1, 2, 3]'::jsonb, '$[*]');
-- 결과: 1, 2, 3

-- 중첩 배열에서 특정 인덱스 접근
SELECT jsonb_path_query(
  '{"users": [{"name": "Alice"}, {"name": "Bob"}]}'::jsonb,
  '$.users[1].name'
);
-- 결과: "Bob"

-- 에러를 방지하는 안전한 방법: jsonb_path_query_first 활용
SELECT jsonb_path_query_first(
  '{"items": [10, 20, 30]}'::jsonb,
  '$.items[0]'
);
-- 결과: 10

-- 경로가 유효한지 먼저 검사
SELECT jsonb_path_exists(
  '{"items": [10, 20, 30]}'::jsonb,
  '$.items[0 to 2]'
);
-- 결과: true

원인 3 해결: JSON 데이터 타입 검사 후 접근

접근 전에 해당 JSON 값이 배열인지 오브젝트인지 먼저 확인한 후 처리하세요.

-- 잘못된 예시: 오브젝트에 배열 인덱스 접근 (22033 에러 발생)
SELECT jsonb_path_query('{"name": "홍길동"}'::jsonb, '$[0]');

-- 올바른 예시: jsonb_typeof()로 타입 확인 후 분기 처리
SELECT
  CASE
    WHEN jsonb_typeof(data) = 'array' THEN
      jsonb_path_query_first(data, '$[0]')::text
    WHEN jsonb_typeof(data) = 'object' THEN
      (data->>'name')
    ELSE
      NULL
  END AS result
FROM (
  VALUES
    ('["Alice", "Bob"]'::jsonb),
    ('{"name": "홍길동"}'::jsonb)
) AS t(data);

-- 실무 예시: 테이블에서 배열인 JSONB 컬럼만 필터링하여 접근
CREATE TABLE user_events (
  id SERIAL PRIMARY KEY,
  payload JSONB NOT NULL
);

INSERT INTO user_events (payload) VALUES
  ('["login", "view", "logout"]'),
  ('{"event": "purchase", "amount": 5000}'),
  ('["signup"]');

-- 배열인 경우에만 첫 번째 요소를 안전하게 추출
SELECT
  id,
  CASE
    WHEN jsonb_typeof(payload) = 'array'
    THEN jsonb_path_query_first(payload, '$[0]')::text
    ELSE payload->>'event'
  END AS first_event
FROM user_events;

예방 방법

1. JSON 데이터 입력 시 스키마 유효성 검사 제약 추가

테이블 설계 단계부터 CHECK 제약 조건을 사용하여 JSONB 컬럼에 저장되는 데이터의 구조를 강제하세요. 이렇게 하면 배열이 와야 할 자리에 오브젝트가 들어오는 것을 사전에 차단할 수 있습니다.

-- 배열 타입만 허용하는 제약 조건 추가
ALTER TABLE user_events
  ADD CONSTRAINT chk_payload_is_array
  CHECK (jsonb_typeof(payload) = 'array');

-- 또는 JSON Schema 검증 함수를 활용한 트리거 방식
CREATE OR REPLACE FUNCTION validate_event_payload()
RETURNS TRIGGER AS $$
BEGIN
  IF jsonb_typeof(NEW.payload) NOT IN ('array', 'object') THEN
    RAISE EXCEPTION 'payload must be a JSON array or object';
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_validate_payload
BEFORE INSERT OR UPDATE ON user_events
FOR EACH ROW EXECUTE FUNCTION validate_event_payload();

2. 애플리케이션 레벨에서 JSON Path 표현식을 동적으로 생성할 때 입력값 검증

동적으로 JSON 경로를 생성하는 경우, 반드시 인덱스 값이 유효한 비음수 정수인지 검증하는 로직을 포함하세요. 또한 PostgreSQL의 jsonb_path_exists() 함수를 먼저 호출하여 경로의 유효성을 선행 검사하는 패턴을 습관화하면 런타임 에러를 크게 줄일 수 있습니다.

-- 안전한 함수 래퍼 예시: 인덱스 유효성 검사 포함
CREATE OR REPLACE FUNCTION safe_jsonb_array_get(
  data JSONB,
  idx INTEGER
)
RETURNS JSONB AS $$
BEGIN
  IF jsonb_typeof(data) != 'array' THEN
    RAISE WARNING 'Input is not a JSON array, returning NULL';
    RETURN NULL;
  END IF;

  IF idx < 0 OR idx >= jsonb_array_length(data) THEN
    RAISE WARNING 'Index % is out of bounds for array of length %', idx, jsonb_array_length(data);
    RETURN NULL;
  END IF;

  RETURN data->idx;
END;
$$ LANGUAGE plpgsql;

-- 사용 예시
SELECT safe_jsonb_array_get('["apple", "banana", "cherry"]'::jsonb, 1);
-- 결과: "banana"

SELECT safe_jsonb_array_get('{"key": "value"}'::jsonb, 0);
-- 결과: NULL (경고 메시지 출력)

관련 에러

  • 22032 (invalid_json_text): JSON 문자열 자체가 문법적으로 올바르지 않을 때 발생하며, 22033과 함께 JSON 처리 에러의 가장 흔한 쌍입니다.
  • 22034 (more_json_paths_than_one_expected): JSON Path 표현식이 하나 이상의 결과를 반환하지만 단일 값을 기대하는 함수에 사용될 때 발생합니다.
  • 22P02 (invalid_text_representation): 유효하지 않은 텍스트를 JSONB로 캐스팅할 때 발생하며, JSON 데이터 파이프라인의 입력 단계에서 자주 등장합니다.
  • 42883 (undefined_function): JSONB 관련 함수를 잘못된 인자 타입과 함께 호출했을 때 발생하며, 22033과 혼동되기 쉽습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기