2026년 06월 12일 | DBMS Error 가이드
이 글에서 다루는 내용
22003 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22003 numeric value out of range 는?
PostgreSQL 에러 코드 22003 numeric value out of range는 숫자형 데이터 타입이 허용하는 범위를 초과하는 값을 저장하거나 연산하려 할 때 발생합니다. 예를 들어 SMALLINT 컬럼에 40,000을 넣거나, NUMERIC(5,2) 컬럼에 1,000.00을 저장하려 할 때 이 에러가 발생합니다. 데이터 마이그레이션, 외부 시스템 연동, 또는 누적 집계 연산 중에 예기치 않게 발생하는 경우가 많아 운영 환경에서 특히 주의가 필요한 에러입니다.
주요 발생 원인
1. 데이터 타입의 저장 범위 초과
가장 흔한 원인으로, 컬럼에 정의된 숫자 타입의 최대값을 넘는 데이터를 삽입하거나 업데이트할 때 발생합니다. PostgreSQL의 정수형 타입별 범위는 다음과 같습니다. SMALLINT는 -32,768 ~ 32,767, INTEGER는 약 -21억 ~ 21억, BIGINT는 약 -922경 ~ 922경입니다. 특히 레거시 시스템에서 마이그레이션할 때 원본 데이터의 범위를 충분히 검토하지 않으면 이 문제가 발생합니다.
2. NUMERIC / DECIMAL 정밀도(Precision) 및 스케일(Scale) 부족
NUMERIC(p, s) 타입은 전체 자릿수(p)와 소수점 이하 자릿수(s)를 명시적으로 지정합니다. NUMERIC(5, 2)는 최대 999.99까지만 저장 가능하며, 1000.00을 저장하려 하면 즉시 22003 에러가 발생합니다. 금융 시스템이나 회계 애플리케이션에서 정밀도 설계를 잘못하면 서비스 장애로 이어질 수 있으므로 초기 설계 단계에서 충분한 여유 자릿수를 확보해야 합니다.
3. 산술 연산 중 오버플로우(Overflow) 발생
단순 삽입이 아닌 연산 결과가 범위를 초과하는 경우도 빈번합니다. INTEGER 컬럼끼리 곱셈을 수행하거나, SUM() 집계 함수를 사용할 때 중간 연산 결과가 타입의 한계를 넘으면 에러가 발생합니다. 특히 배치 작업이나 통계 집계 쿼리에서 대용량 데이터를 처리할 때 자주 발생하므로, 연산 전에 명시적 타입 캐스팅을 습관화해야 합니다.
해결 방법
원인 1 해결: 컬럼 타입 변경 (ALTER TABLE)
기존 컬럼의 데이터 타입을 더 큰 범위를 수용하는 타입으로 변경합니다.
-- 문제 상황: SMALLINT 컬럼에 40000 삽입 시도
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
quantity SMALLINT -- 최대 32767까지만 허용
);
INSERT INTO orders (quantity) VALUES (40000);
-- ERROR: smallint out of range
-- 해결: INTEGER로 타입 업그레이드
ALTER TABLE orders
ALTER COLUMN quantity TYPE INTEGER;
-- 이제 정상 삽입 가능
INSERT INTO orders (quantity) VALUES (40000);
-- INSERT 0 1
-- 타입별 범위 확인 쿼리
SELECT
pg_catalog.format_type(t.oid, NULL) AS type_name,
t.typlen AS byte_length
FROM pg_type t
WHERE t.typname IN ('int2', 'int4', 'int8', 'numeric', 'float4', 'float8')
ORDER BY t.typlen;
원인 2 해결: NUMERIC 정밀도 및 스케일 조정
-- 문제 상황: NUMERIC(5,2) 컬럼에 1000.00 저장 시도
CREATE TABLE products (
id SERIAL PRIMARY KEY,
price NUMERIC(5, 2) -- 최대 999.99까지만 저장 가능
);
INSERT INTO products (price) VALUES (1000.00);
-- ERROR: numeric field overflow
-- DETAIL: A field with precision 5, scale 2 must round to an absolute value less than 10^3.
-- 해결: 정밀도를 충분히 늘린 타입으로 변경
ALTER TABLE products
ALTER COLUMN price TYPE NUMERIC(12, 2);
INSERT INTO products (price) VALUES (1000.00);
-- INSERT 0 1
INSERT INTO products (price) VALUES (9999999999.99);
-- INSERT 0 1 (최대 9,999,999,999.99 저장 가능)
-- 현재 컬럼의 NUMERIC 정밀도 확인
SELECT
column_name,
data_type,
numeric_precision,
numeric_scale
FROM information_schema.columns
WHERE table_name = 'products'
AND column_name = 'price';
원인 3 해결: 연산 중 명시적 타입 캐스팅
-- 문제 상황: INTEGER 컬럼 간 곱셈 오버플로우
CREATE TABLE sales (
id SERIAL PRIMARY KEY,
unit_price INTEGER,
quantity INTEGER
);
INSERT INTO sales (unit_price, quantity) VALUES (100000, 50000);
-- 오버플로우 발생 가능한 쿼리
SELECT unit_price * quantity AS total_amount FROM sales;
-- ERROR: integer out of range
-- 해결 1: BIGINT로 명시적 캐스팅
SELECT unit_price::BIGINT * quantity::BIGINT AS total_amount FROM sales;
-- 결과: 5000000000
-- 해결 2: CAST 함수 사용
SELECT CAST(unit_price AS BIGINT) * CAST(quantity AS BIGINT) AS total_amount FROM sales;
-- 해결 3: SUM() 집계 오버플로우 방지
SELECT SUM(unit_price::BIGINT * quantity::BIGINT) AS grand_total
FROM sales;
-- 해결 4: 연산 전 범위 검증 쿼리
SELECT
unit_price,
quantity,
CASE
WHEN unit_price::BIGINT * quantity::BIGINT > 2147483647
THEN '오버플로우 위험'
ELSE '정상 범위'
END AS range_check
FROM sales;
긴급 데이터 확인 및 클린업
-- 범위를 초과하는 데이터 사전 탐지
SELECT id, quantity
FROM orders
WHERE quantity > 32767 OR quantity < -32768;
-- 삽입 전 범위 검증 함수 생성
CREATE OR REPLACE FUNCTION safe_insert_quantity(p_quantity BIGINT)
RETURNS VOID AS $$
BEGIN
IF p_quantity > 2147483647 OR p_quantity < -2147483648 THEN
RAISE EXCEPTION '22003'
USING MESSAGE = '입력값이 INTEGER 범위를 초과합니다: ' || p_quantity,
HINT = 'BIGINT 타입 사용을 고려하세요.';
END IF;
INSERT INTO orders (quantity) VALUES (p_quantity::INTEGER);
END;
$$ LANGUAGE plpgsql;
예방 방법
1. 초기 스키마 설계 시 충분한 여유 자릿수 확보
테이블 설계 단계에서 현재 데이터 범위만 보지 말고, 향후 5~10년간의 데이터 증가를 고려해 타입을 선택해야 합니다. 특히 금액, 카운터, 집계값 컬럼은 처음부터 BIGINT나 NUMERIC(15, 2) 이상으로 설계하는 것을 권장합니다. 아래는 타입 선택 가이드라인 쿼리입니다.
-- 운영 중인 테이블의 실제 최대값 모니터링 뷰 생성
CREATE OR REPLACE VIEW v_numeric_range_monitor AS
SELECT
'orders.quantity' AS column_ref,
MAX(quantity) AS current_max,
MIN(quantity) AS current_min,
32767 AS type_max,
ROUND((MAX(quantity)::NUMERIC / 32767) * 100, 2) AS usage_pct
FROM orders
UNION ALL
SELECT
'sales.unit_price',
MAX(unit_price),
MIN(unit_price),
2147483647,
ROUND((MAX(unit_price)::NUMERIC / 2147483647) * 100, 2)
FROM sales;
-- 사용률 80% 초과 컬럼 알림
SELECT column_ref, current_max, type_max, usage_pct
FROM v_numeric_range_monitor
WHERE usage_pct > 80;
2. 데이터 입력 전 CHECK 제약 조건 및 도메인 타입 활용
-- CHECK 제약 조건으로 비즈니스 규칙에 맞는 범위 제한
ALTER TABLE products
ADD CONSTRAINT chk_price_range
CHECK (price > 0 AND price < 100000000);
-- 도메인 타입으로 재사용 가능한 타입 정의
CREATE DOMAIN money_amount AS NUMERIC(15, 2)
CHECK (VALUE >= 0 AND VALUE < 1000000000000);
-- 도메인 타입 적용
CREATE TABLE invoices (
id SERIAL PRIMARY KEY,
total_amount money_amount,
tax_amount money_amount
);
관련 에러
- 22001
string_data_right_truncation: 문자열 데이터가 컬럼 길이를 초과할 때 발생하며, 22003과 유사하게 타입 크기 제한 위반 에러입니다. - 22P02
invalid_text_representation: 숫자형 컬럼에 문자열을 잘못 캐스팅할 때 발생하며, 22003과 함께 데이터 타입 불일치 에러군에 속합니다. - 22012
division_by_zero: 산술 연산 중 발생하는 에러로, 22003과 마찬가지로 집계 및 연산 쿼리 작성 시 함께 방어 코드를 작성해야 합니다. - 22023
invalid_parameter_value: 함수의 파라미터 값이 허용 범위를 벗어날 때 발생하며, 광의의 범위 초과 에러 계열에 포함됩니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.