2026년 06월 20일 | DBMS Error 가이드
이 글에서 다루는 내용
2203F 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
2203F sql json scalar required 는?
PostgreSQL 에러 코드 2203F: sql_json_scalar_required는 SQL/JSON 경로 표현식 또는 JSON 함수를 사용할 때, 스칼라 값(문자열, 숫자, 불리언, null 등 단일 값)이 반환되어야 하는 맥락에서 배열이나 객체와 같은 복합(composite) JSON 값이 반환되었을 때 발생합니다. 이 에러는 주로 JSON_VALUE(), jsonb_path_query_first() 또는 SQL 표준 JSON 경로 함수들을 사용할 때 나타나며, PostgreSQL 12 이상 버전에서 SQL/JSON 표준 준수 기능이 강화되면서 더욱 엄격하게 적용되고 있습니다. 즉, 단일 원시 값을 기대하는 함수에 JSON 배열([])이나 JSON 객체({})를 그대로 반환하려 할 때 이 에러가 트리거됩니다.
주요 발생 원인
1. JSON_VALUE() 함수에서 스칼라가 아닌 배열 또는 객체를 반환하려는 경우
JSON_VALUE() 함수는 SQL 표준에 따라 반드시 스칼라 값 하나만 반환해야 합니다. JSON 경로 표현식이 배열([1, 2, 3])이나 객체({"key": "value"})를 가리키고 있을 때, JSON_VALUE()는 이를 처리하지 못하고 2203F 에러를 발생시킵니다. 이는 가장 흔한 원인이며, JSON 구조를 충분히 파악하지 않은 채 경로를 작성할 때 자주 발생합니다.
2. 중첩된 JSON 구조에서 잘못된 경로 표현식 사용
JSON 데이터가 깊게 중첩된 구조를 가질 때, 경로 표현식이 중간 객체나 배열을 가리키는 경우가 있습니다. 예를 들어 $.address 경로가 스칼라 문자열이 아닌 {"city": "Seoul", "zip": "04524"} 객체를 반환하도록 설계된 데이터에서 이를 JSON_VALUE()로 조회하면 에러가 발생합니다. 경로를 $.address.city처럼 최종 스칼라 값까지 명시적으로 지정해야 합니다.
3. 동적으로 생성된 JSON 데이터의 타입 불일치
애플리케이션에서 동적으로 생성된 JSON 데이터는 필드가 상황에 따라 스칼라가 될 수도 있고 배열이 될 수도 있는 경우가 있습니다. 예를 들어 tags 필드가 어떤 레코드에서는 "postgresql"(스칼라)이고, 다른 레코드에서는 ["postgresql", "database"](배열)인 경우, JSON_VALUE()로 일괄 처리하면 배열인 레코드에서 2203F 에러가 발생합니다. 이런 비정형 데이터는 쿼리 전 타입 체크 로직이 반드시 필요합니다.
해결 방법
원인 1 해결: JSON_VALUE() 대신 적절한 함수 선택
배열이나 객체를 다루어야 한다면 JSON_VALUE() 대신 JSON_QUERY()를 사용하세요. JSON_QUERY()는 스칼라뿐 아니라 객체와 배열도 반환할 수 있습니다.
-- 에러가 발생하는 쿼리 (배열을 JSON_VALUE로 조회)
SELECT JSON_VALUE('{"items": [1, 2, 3]}', '$.items');
-- ERROR: 2203F: SQL/JSON scalar required
-- 해결책 1: JSON_QUERY() 사용 (배열/객체 반환 가능)
SELECT JSON_QUERY('{"items": [1, 2, 3]}', '$.items');
-- 결과: [1, 2, 3]
-- 해결책 2: 경로를 스칼라 값까지 명시
SELECT JSON_VALUE('{"items": [1, 2, 3]}', '$.items[0]');
-- 결과: 1
-- 해결책 3: ON ERROR 절을 사용하여 에러를 NULL로 처리
SELECT JSON_VALUE('{"items": [1, 2, 3]}', '$.items' NULL ON ERROR);
-- 결과: NULL (에러 대신 NULL 반환)
원인 2 해결: 중첩 경로를 스칼라 값까지 명시적으로 지정
-- 에러가 발생하는 쿼리
SELECT JSON_VALUE(
'{"user": {"name": "Alice", "address": {"city": "Seoul", "zip": "04524"}}}',
'$.user.address'
);
-- ERROR: 2203F: SQL/JSON scalar required
-- 해결책: 스칼라 값까지 경로 명시
SELECT JSON_VALUE(
'{"user": {"name": "Alice", "address": {"city": "Seoul", "zip": "04524"}}}',
'$.user.address.city'
);
-- 결과: Seoul
-- 실무 테이블 예시
CREATE TABLE user_profiles (
id SERIAL PRIMARY KEY,
profile JSONB
);
INSERT INTO user_profiles (profile) VALUES
('{"name": "Alice", "address": {"city": "Seoul", "zip": "04524"}}'),
('{"name": "Bob", "address": {"city": "Busan", "zip": "48058"}}');
-- 올바른 스칼라 경로 쿼리
SELECT
id,
JSON_VALUE(profile::json, '$.name') AS name,
JSON_VALUE(profile::json, '$.address.city') AS city
FROM user_profiles;
원인 3 해결: 타입 체크 후 조건부 처리
동적 JSON 데이터에서 필드가 스칼라인지 배열인지 사전 검사한 뒤 처리합니다.
-- 비정형 tags 필드를 가진 테이블 예시
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
data JSONB
);
INSERT INTO articles (data) VALUES
('{"title": "PostgreSQL Tips", "tags": "database"}'),
('{"title": "JSON in PG", "tags": ["postgresql", "json", "database"]}');
-- jsonb_typeof()로 타입 확인 후 분기 처리
SELECT
id,
data->>'title' AS title,
CASE
WHEN jsonb_typeof(data->'tags') = 'array' THEN data->'tags'->>0
WHEN jsonb_typeof(data->'tags') = 'string' THEN data->>'tags'
ELSE NULL
END AS first_tag
FROM articles;
-- jsonb_path_query_first()와 NULL ON ERROR 패턴 활용
SELECT
id,
jsonb_path_query_first(data, '$.tags[0]') AS first_tag_jsonb
FROM articles;
-- JSON_VALUE with NULL ON ERROR (안전한 폴백)
SELECT
id,
JSON_VALUE(data::json, '$.tags' NULL ON ERROR) AS tag_scalar_or_null
FROM articles;
예방 방법
1. JSON 스키마를 사전에 정의하고 CHECK 제약 조건으로 강제하기
데이터를 삽입할 때부터 JSON 구조의 타입을 강제하면 쿼리 시점에 발생하는 타입 불일치 에러를 원천 차단할 수 있습니다. jsonb_typeof()를 활용한 CHECK 제약 조건을 테이블 정의에 추가하고, 가능하다면 JSON Schema 검증 확장(예: pg_jsonschema)을 도입하는 것을 권장합니다.
-- CHECK 제약 조건으로 tags 필드를 항상 배열로 강제
ALTER TABLE articles
ADD CONSTRAINT chk_tags_is_array
CHECK (jsonb_typeof(data->'tags') = 'array' OR data->'tags' IS NULL);
-- pg_jsonschema 확장 활용 예 (설치된 경우)
-- CREATE EXTENSION IF NOT EXISTS pg_jsonschema;
-- ALTER TABLE articles ADD CONSTRAINT chk_json_schema
-- CHECK (validate_json_schema('{"type":"object","properties":{"tags":{"type":"array"}}}', data));
2. JSON_VALUE() 사용 시 항상 ON ERROR 절을 명시하고, 경로 표현식을 단위 테스트로 검증하기
프로덕션 코드에 JSON_VALUE()를 사용할 때는 반드시 NULL ON ERROR 또는 DEFAULT '...' ON ERROR 절을 명시하여 예기치 않은 에러가 서비스에 영향을 미치지 않도록 하세요. 또한 CI/CD 파이프라인에 JSON 경로 표현식의 단위 테스트를 포함시켜 데이터 구조 변경 시 즉시 감지할 수 있도록 합니다.
-- 권장 패턴: 항상 ON ERROR 절 명시
SELECT JSON_VALUE(
data::json,
'$.address.city'
DEFAULT 'Unknown' ON ERROR
) AS city
FROM user_profiles;
관련 에러
22032:invalid_json_text— JSON 문자열 자체가 유효하지 않은 형식일 때 발생하며,2203F와 달리 파싱 단계에서 실패합니다.2203E:sql_json_array_not_found— JSON 경로 결과가 배열이어야 하는 맥락에서 스칼라나 객체가 반환될 때 발생하며,2203F의 반대 상황입니다.2203W:sql_json_object_not_found— JSON 경로 결과가 객체이어야 하는 맥락에서 스칼라나 배열이 반환될 때 발생합니다.22033:invalid_sql_json_subscript— JSON 배열 인덱스 접근 시 유효하지 않은 서브스크립트를 사용했을 때 발생합니다.
이 에러들은 모두 SQL/JSON 경로 처리 과정에서 발생하며, 공통적으로 ON ERROR 절을 통해 안전하게 폴백 처리할 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.