2026년 06월 18일 | DBMS Error 가이드
이 글에서 다루는 내용
22037 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22037 non unique keys in a json object 는?
PostgreSQL 에러 코드 22037은 JSON 객체 내에 동일한 키(key)가 두 번 이상 중복되어 사용될 때 발생하는 오류입니다. JSON 표준(RFC 7159)에서는 객체 내 키의 유일성을 권장하지만, PostgreSQL의 jsonb 타입은 중복 키를 명시적으로 허용하지 않으며, 이 규칙을 위반했을 때 이 에러를 발생시킵니다. 주로 외부 시스템에서 생성된 JSON 데이터를 jsonb 컬럼에 삽입하거나, json_object(), jsonb_build_object() 등의 함수를 사용할 때 중복 키가 포함된 경우 이 에러가 트리거됩니다.
주요 발생 원인
- jsonb_build_object() 또는 json_object() 함수에서 중복 키 사용
가장 흔한 원인 중 하나입니다. 개발자가 동적으로 JSON 객체를 생성할 때 프로그래밍 로직 오류나 반복문 처리 실수로 인해 동일한 키 이름이 두 번 이상 전달되는 경우입니다. 특히 여러 테이블의 컬럼을 동적으로 JSON으로 변환하는 쿼리에서 컬럼명이 겹칠 때 자주 발생합니다.
- 외부 API 또는 애플리케이션에서 생성된 비표준 JSON 입력
외부 서드파티 API나 레거시 애플리케이션에서 생성된 JSON 데이터에는 간혹 중복된 키가 포함될 수 있습니다. json 타입은 이를 그대로 저장하지만, jsonb 타입으로 저장하거나 변환하는 순간 PostgreSQL이 엄격하게 검증하여 에러를 발생시킵니다. 이런 데이터가 ETL 파이프라인이나 데이터 마이그레이션 과정에서 유입될 경우 배치 작업 전체가 실패할 수 있습니다.
- jsonb 집계 함수 또는 JSONB 병합 연산에서의 키 충돌
jsonb_agg(), jsonb_object_agg() 등의 집계 함수를 사용하거나, || 연산자로 두 JSONB 객체를 병합할 때 동일한 키가 존재하는 경우 에러가 발생할 수 있습니다. 특히 jsonb_object_agg()는 그룹 내에 동일한 키 값이 존재하면 명시적으로 에러를 던지는 동작을 합니다.
해결 방법
원인 1: jsonb_build_object()에서 중복 키 제거
중복 키를 포함한 잘못된 쿼리와 수정된 쿼리를 비교해 보겠습니다.
-- ❌ 에러 발생: 'name' 키가 두 번 사용됨
SELECT jsonb_build_object(
'name', '홍길동',
'age', 30,
'name', '홍길순' -- 중복 키!
);
-- ERROR: 22037: non unique keys in a json object
-- ✅ 수정: 키 이름을 명확하게 구분
SELECT jsonb_build_object(
'first_name', '홍길동',
'age', 30,
'second_name', '홍길순'
);
-- ✅ 실무 예시: 동적 컬럼 조합 시 별칭을 활용한 안전한 JSON 생성
SELECT jsonb_build_object(
'user_name', u.name,
'order_name', o.name, -- 컬럼명이 겹칠 때 별칭으로 구분
'amount', o.amount
)
FROM users u
JOIN orders o ON u.id = o.user_id;
원인 2: 외부 JSON 입력의 중복 키 처리
외부 데이터를 안전하게 jsonb로 변환하는 방법입니다.
-- 중복 키가 있는 json 문자열 (json 타입은 허용하지만 jsonb는 거부)
-- ❌ 에러 발생
SELECT '{"id": 1, "name": "test", "name": "duplicate"}'::jsonb;
-- ERROR: 22037: non unique keys in a json object
-- ✅ 해결책 1: json으로 먼저 받은 뒤 필요한 키만 추출하여 jsonb로 변환
WITH raw_data AS (
SELECT '{"id": 1, "name": "test", "name": "duplicate"}'::json AS raw_json
)
SELECT jsonb_build_object(
'id', (raw_json->>'id')::int,
'name', raw_json->>'name' -- 첫 번째 'name' 값만 가져옴
)
FROM raw_data;
-- ✅ 해결책 2: 입력 데이터 검증 함수 생성
CREATE OR REPLACE FUNCTION safe_to_jsonb(input_text TEXT)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
DECLARE
result jsonb;
BEGIN
BEGIN
result := input_text::jsonb;
EXCEPTION WHEN sqlstate '22037' THEN
-- 중복 키 발생 시 json으로 파싱 후 재구성 (마지막 키 값 우선)
RAISE WARNING '중복 키가 발견되었습니다. 마지막 값을 사용합니다: %', input_text;
SELECT jsonb_object_agg(key, value)
INTO result
FROM json_each(input_text::json);
END;
RETURN result;
END;
$$;
-- 함수 사용 예시
SELECT safe_to_jsonb('{"id": 1, "name": "test", "name": "duplicate"}');
원인 3: jsonb_object_agg() 집계 시 중복 키 방지
-- 테스트 데이터 준비
CREATE TEMP TABLE product_attrs (
product_id INT,
attr_key TEXT,
attr_value TEXT
);
INSERT INTO product_attrs VALUES
(1, 'color', 'red'),
(1, 'size', 'large'),
(1, 'color', 'blue'); -- 'color' 키 중복!
-- ❌ 에러 발생
SELECT product_id, jsonb_object_agg(attr_key, attr_value)
FROM product_attrs
GROUP BY product_id;
-- ERROR: 22037: non unique keys in a json object
-- ✅ 해결책 1: DISTINCT ON으로 중복 키 제거 (첫 번째 값 우선)
SELECT product_id, jsonb_object_agg(attr_key, attr_value)
FROM (
SELECT DISTINCT ON (product_id, attr_key)
product_id, attr_key, attr_value
FROM product_attrs
ORDER BY product_id, attr_key
) deduped
GROUP BY product_id;
-- ✅ 해결책 2: 중복 키가 있을 경우 값을 배열로 묶기
SELECT
product_id,
jsonb_object_agg(attr_key, attr_values) AS attributes
FROM (
SELECT
product_id,
attr_key,
jsonb_agg(attr_value) AS attr_values
FROM product_attrs
GROUP BY product_id, attr_key
) grouped
GROUP BY product_id;
-- 결과: {"color": ["red", "blue"], "size": ["large"]}
예방 방법
- jsonb 컬럼에 입력되는 JSON 데이터를 애플리케이션 레이어에서 사전 검증하세요.
데이터베이스에 저장되기 전, 애플리케이션 코드 수준에서 JSON 객체의 키 유일성을 검증하는 로직을 추가하는 것이 가장 효과적입니다. Python의 경우 json.loads() 후 딕셔너리 키 개수와 원본 파싱 결과를 비교하거나, 커스텀 파서를 사용하여 중복 키를 감지할 수 있습니다. ETL 파이프라인에서는 PostgreSQL에 적재하기 전 검증 단계를 파이프라인에 포함시키는 것을 권장합니다.
“`sql
— 저장 전 json -> jsonb 변환 가능 여부를 확인하는 체크 제약 조건 추가
ALTER TABLE my_table
ADD CONSTRAINT check_valid_jsonb
CHECK (meta_data IS NULL OR (meta_data::text)::jsonb IS NOT NULL);
— (실제로는 jsonb 컬럼 자체가 입력 시 검증하므로, json 타입 컬럼에 적용 가능)
— json 타입 컬럼에서 jsonb로 마이그레이션 전 중복 키 사전 탐지
SELECT id, raw_json
FROM legacy_table
WHERE raw_json IS NOT NULL
AND (
SELECT COUNT(*) FROM json_object_keys(raw_json)
) != (
SELECT COUNT(DISTINCT key) FROM json_object_keys(raw_json) AS key
);
“`
jsonb타입을json타입보다 우선 사용하고, 동적 JSON 생성 시 키 네이밍 컨벤션을 엄격히 관리하세요.
json 타입은 중복 키를 허용하여 일시적으로 에러를 숨길 수 있지만, 이후 jsonb로 캐스팅하거나 인덱스를 생성할 때 문제가 드러납니다. 처음부터 jsonb 타입을 사용하면 데이터 입력 단계에서 즉시 중복을 감지할 수 있어 더 안전합니다. 또한 팀 내 JSON 키 네이밍 컨벤션(예: 테이블명_컬럼명 형식)을 문서화하고, 코드 리뷰 과정에서 jsonb_build_object() 호출의 키 중복 여부를 반드시 확인하는 체크리스트를 도입하세요.
관련 에러
- 22P02 (invalid_text_representation): JSON 자체가 문법적으로 잘못된 경우 발생하며, 22037과 함께 JSON 데이터 유효성 검사 시 함께 처리해야 합니다.
- 22023 (invalid_parameter_value):
jsonb_build_object()에 홀수 개의 인자를 전달하는 등 잘못된 파라미터 사용 시 발생합니다. - 42703 (undefined_column): JSONB 연산자로 존재하지 않는 키에 접근할 때 관련될 수 있으며, JSON 구조 불일치 문제와 함께 발생하는 경우가 많습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.