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

22039
2026년 06월 18일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22039 sql json array not found 는?

PostgreSQL 에러 코드 22039sql_json_array_not_found 오류로, SQL/JSON 경로 표현식을 사용할 때 배열(Array)을 기대하는 컨텍스트에서 배열이 발견되지 않았을 때 발생합니다. 이 에러는 주로 jsonb_path_query_array(), JSON_TABLE(), JSON_QUERY() 등 PostgreSQL 14 이상에서 지원하는 SQL/JSON 함수 및 경로 연산자를 활용할 때 나타납니다. 특히 ON ERROR 절이나 ON EMPTY 절을 명시하지 않거나 잘못된 JSON 구조에 접근하려 할 때 예외가 발생하며, 운영 환경에서 JSON 데이터를 다루는 쿼리가 많아질수록 자주 마주치는 오류 중 하나입니다.


주요 발생 원인

1. JSON 경로가 배열이 아닌 객체나 스칼라 값을 반환할 때

가장 빈번하게 발생하는 원인입니다. JSON_QUERY() 또는 jsonb_path_query_array() 함수에서 특정 경로가 JSON 배열이 아닌 객체({}) 또는 숫자, 문자열 같은 스칼라 값을 반환할 경우 이 에러가 발생합니다. 예를 들어 경로 표현식이 $.user를 가리키는데 실제 값이 배열이 아니라 단일 JSON 객체일 때 PostgreSQL은 배열 컨텍스트에서 처리를 강제하다 예외를 던집니다.

2. ON ERROR / ON EMPTY 절 누락으로 인한 엄격 모드 동작

PostgreSQL의 SQL/JSON 함수들은 기본적으로 엄격(strict) 또는 관대(lax) 모드로 동작하지만, JSON_QUERY() 같은 함수에서 WITH ARRAY WRAPPER 옵션이나 ON ERROR ERROR 절을 사용하는 경우, 경로 결과가 배열이 아니면 즉시 오류를 발생시킵니다. 많은 개발자들이 이 옵션의 기본 동작을 간과하여 예외 처리 없이 쿼리를 작성하고, 실제 데이터가 예상 구조와 다를 때 운영 중 에러가 터지는 상황이 발생합니다.

3. 불규칙하거나 일관성 없는 JSON 데이터 구조

애플리케이션에서 JSON 데이터를 삽입할 때 항상 동일한 구조를 보장하지 않는 경우에 발생합니다. 예를 들어 어떤 레코드는 tags 필드가 ["a", "b"]처럼 배열이지만, 다른 레코드는 "a"처럼 단일 문자열이거나 아예 null인 경우가 있을 수 있습니다. 이처럼 스키마 검증 없이 다양한 클라이언트나 버전에서 적재된 데이터를 처리할 때 특정 행에서만 에러가 발생해 디버깅이 까다로워집니다.


해결 방법

원인 1 해결: WITH ARRAY WRAPPER 또는 LAX 모드 활용

배열이 아닌 값을 배열로 자동 래핑하여 처리하거나, lax 모드를 사용하여 에러 대신 빈 결과를 반환하도록 합니다.

-- 문제가 되는 쿼리 예시 (배열이 아닌 단일 객체를 배열로 기대)
SELECT JSON_QUERY('{"user": {"name": "Alice"}}', '$.user' WITH ARRAY WRAPPER);
-- 결과: [{"name": "Alice"}] 로 자동 래핑

-- LAX 모드 사용: 배열이 없어도 에러 대신 NULL 또는 빈 값 반환
SELECT JSON_QUERY(
    '{"user": {"name": "Alice"}}',
    'lax $.items'
    EMPTY ARRAY ON EMPTY
    NULL ON ERROR
);
-- items가 없어도 NULL 반환으로 에러 방지

원인 2 해결: ON ERROR 절을 통한 명시적 예외 처리

JSON_QUERY(), JSON_VALUE() 사용 시 반드시 ON ERROR 절을 명시하여 에러가 발생하더라도 쿼리가 중단되지 않도록 합니다.

-- ON ERROR 절로 에러 발생 시 NULL 반환
SELECT JSON_QUERY(
    data,
    '$.tags'
    WITH CONDITIONAL ARRAY WRAPPER
    NULL ON ERROR
    EMPTY ARRAY ON EMPTY
)
FROM user_profiles;

-- jsonb_path_query_array 사용 시 try 버전 활용
SELECT jsonb_path_query_array(data, '$.tags')
FROM user_profiles
WHERE jsonb_path_exists(data, '$.tags')
  AND jsonb_typeof(data -> 'tags') = 'array';

원인 3 해결: 데이터 삽입 전 타입 검증 및 정규화

데이터를 조회하기 전에 jsonb_typeof()로 해당 필드가 실제 배열인지 확인하고, 배열이 아닌 경우 정규화합니다.

-- 조회 전 타입 체크
SELECT id,
       CASE
           WHEN jsonb_typeof(data -> 'tags') = 'array' THEN data -> 'tags'
           WHEN jsonb_typeof(data -> 'tags') = 'string' THEN jsonb_build_array(data -> 'tags')
           WHEN data -> 'tags' IS NULL THEN '[]'::jsonb
           ELSE '[]'::jsonb
       END AS tags_normalized
FROM user_profiles;

-- 기존 데이터 일괄 정규화 (단일 문자열을 배열로 변환)
UPDATE user_profiles
SET data = jsonb_set(
    data,
    '{tags}',
    jsonb_build_array(data -> 'tags')
)
WHERE jsonb_typeof(data -> 'tags') <> 'array'
  AND data ? 'tags';

-- JSON_TABLE을 사용할 때 안전하게 처리
SELECT jt.*
FROM user_profiles,
     JSON_TABLE(
         CASE
             WHEN jsonb_typeof(data -> 'tags') = 'array' THEN data
             ELSE jsonb_set(data, '{tags}', '[]'::jsonb)
         END,
         '$.tags[*]' COLUMNS (
             tag_value TEXT PATH '$'
         )
     ) AS jt;

예방 방법

1. JSON 데이터 삽입 시 CHECK 제약 조건으로 스키마 강제

테이블 정의 단계에서 CHECK 제약 조건을 추가하여 특정 키가 항상 배열 타입임을 보장합니다. 이렇게 하면 애플리케이션이나 ETL 파이프라인에서 잘못된 구조의 데이터가 DB에 적재되는 것을 원천적으로 차단할 수 있습니다.

-- 테이블 생성 시 tags 필드가 항상 배열임을 강제
CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    data JSONB NOT NULL,
    CONSTRAINT chk_tags_is_array
        CHECK (
            (data -> 'tags') IS NULL
            OR jsonb_typeof(data -> 'tags') = 'array'
        )
);

-- 기존 테이블에 제약 조건 추가
ALTER TABLE user_profiles
ADD CONSTRAINT chk_tags_is_array
CHECK (
    (data -> 'tags') IS NULL
    OR jsonb_typeof(data -> 'tags') = 'array'
);

2. SQL/JSON 함수 사용 시 항상 ON ERROR / ON EMPTY 절 명시 표준화

팀 내 SQL 작성 가이드라인에 JSON_QUERY(), JSON_VALUE() 등 SQL/JSON 함수를 사용할 때 반드시 ON ERRORON EMPTY 절을 명시하도록 규정합니다. 이를 통해 예상치 못한 데이터 구조 변화에도 쿼리가 안정적으로 동작하며, 코드 리뷰 체크리스트에 포함시켜 제도적으로 관리하는 것이 좋습니다.

-- 권장 패턴: 모든 JSON_QUERY에 ON ERROR/ON EMPTY 명시
SELECT
    id,
    JSON_QUERY(data, 'lax $.tags' WITH ARRAY WRAPPER NULL ON ERROR EMPTY ARRAY ON EMPTY) AS tags,
    JSON_VALUE(data, 'lax $.user.name' DEFAULT 'Unknown' ON ERROR NULL ON EMPTY) AS username
FROM user_profiles;

관련 에러

  • 22032 (invalid_json_text): 유효하지 않은 JSON 문자열 자체를 파싱할 때 발생하며, JSON 데이터 적재 단계에서 자주 동반됩니다.
  • 22033 (impossible_json_subscript): JSON 배열의 유효하지 않은 인덱스에 접근할 때 발생하며, 22039와 함께 배열 처리 로직에서 함께 고려해야 합니다.
  • 22034 (json_number_not_found): 경로 표현식이 숫자 값을 기대하는데 다른 타입이 반환될 때 발생합니다. 22039와 유사한 패턴으로, 경로 결과 타입 불일치 계열 에러입니다.
  • 22035 (json_object_not_found): 배열 대신 객체를 기대하는 상황에서 객체가 없을 때 발생하며, 22039와 반대 상황으로 자주 쌍으로 검토됩니다.
  • 2203A (too_many_json_array_elements): 배열 요소 수가 제한을 초과할 때 발생하며, 배열 처리 관련 에러군에 속합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기