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

22000
2026년 08월 07일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22000 data exception 는?

PostgreSQL 에러 코드 22000데이터 예외(data exception) 를 나타내는 최상위 에러 클래스입니다. 이 에러는 SQL 연산 중 데이터 값이 해당 컨텍스트에서 유효하지 않거나 허용되지 않는 형태일 때 발생하며, 주로 타입 변환 실패, 범위 초과, 잘못된 문자열 포맷 등의 상황에서 트리거됩니다. 22000은 단독으로 발생하기보다는 22001(string_data_right_truncation), 22003(numeric_value_out_of_range) 등 더 구체적인 하위 에러 코드의 부모 클래스로 작동하므로, 실무에서는 하위 코드까지 함께 확인하는 습관이 중요합니다.


주요 발생 원인

1. 잘못된 타입 변환 (Invalid Type Cast)

가장 흔한 원인 중 하나로, 문자열을 숫자, 날짜, UUID 등의 타입으로 강제 변환할 때 해당 값이 형식에 맞지 않으면 발생합니다. 예를 들어 'abc'INTEGER로 캐스팅하거나, '2024-13-45'처럼 존재하지 않는 날짜를 DATE 타입으로 변환하려 할 때 이 에러가 발생합니다. 특히 외부 시스템이나 CSV 파일에서 데이터를 적재하는 ETL 파이프라인에서 자주 목격됩니다.

2. 숫자 값의 범위 초과 (Numeric Value Out of Range)

SMALLINT, INTEGER, BIGINT, NUMERIC(p, s) 같은 숫자형 컬럼에 허용 범위를 벗어나는 값을 삽입하거나 산술 연산 결과가 오버플로우될 때 발생합니다. 예컨대 SMALLINT의 최대값은 32767인데 이를 초과하는 값을 넣거나, NUMERIC(5, 2)1000.00처럼 정수부가 허용 자릿수를 초과하는 경우가 해당됩니다. 집계 함수나 누적 계산 쿼리에서 의도치 않게 발생하기도 합니다.

3. 문자열 길이 초과 또는 잘못된 포맷 (String Truncation / Invalid Format)

VARCHAR(n) 또는 CHAR(n) 컬럼에 허용된 길이보다 긴 문자열을 삽입하려 할 때 발생합니다. 또한 정규식 패턴이나 특정 포맷(예: ISO 8601 날짜, JSON 형식)을 기대하는 컬럼이나 함수에 형식에 맞지 않는 값이 전달될 때도 이 범주의 에러가 발생합니다. 사용자 입력값을 검증 없이 그대로 DB에 저장하는 구조에서 특히 취약합니다.


해결 방법

원인 1: 잘못된 타입 변환 해결

TRY-방식의 안전한 캐스팅을 사용하여 변환 실패 시 NULL을 반환하도록 처리합니다.

-- 문제 발생 쿼리
SELECT CAST('abc' AS INTEGER);
-- ERROR: invalid input syntax for type integer: "abc"

-- 해결 방법 1: 정규식으로 사전 검증
SELECT
    raw_value,
    CASE
        WHEN raw_value ~ '^-?[0-9]+$' THEN raw_value::INTEGER
        ELSE NULL
    END AS safe_integer
FROM staging_table;

-- 해결 방법 2: PostgreSQL 함수를 활용한 안전한 캐스트 래퍼
CREATE OR REPLACE FUNCTION safe_cast_to_int(p_value TEXT)
RETURNS INTEGER AS $$
BEGIN
    RETURN p_value::INTEGER;
EXCEPTION
    WHEN data_exception THEN
        RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- 사용 예시
SELECT safe_cast_to_int('123');   -- 123 반환
SELECT safe_cast_to_int('abc');   -- NULL 반환

-- 날짜 변환 안전 처리
SELECT
    raw_date,
    CASE
        WHEN raw_date ~ '^\d{4}-\d{2}-\d{2}$'
            THEN raw_date::DATE
        ELSE NULL
    END AS safe_date
FROM import_data;

원인 2: 숫자 범위 초과 해결

컬럼 정의를 충분한 범위의 타입으로 변경하거나, 삽입 전 범위를 검사합니다.

-- 문제 발생 예시
CREATE TABLE orders (
    order_id SMALLINT PRIMARY KEY,
    quantity SMALLINT
);

INSERT INTO orders VALUES (1, 40000);
-- ERROR: smallint out of range

-- 해결 방법 1: 컬럼 타입을 더 큰 범위로 변경
ALTER TABLE orders ALTER COLUMN quantity TYPE INTEGER;

-- 해결 방법 2: 삽입 전 범위 클리핑 (비즈니스 로직 허용 시)
INSERT INTO orders (order_id, quantity)
VALUES (
    1,
    LEAST(GREATEST(:input_quantity, -32768), 32767)
);

-- 해결 방법 3: NUMERIC 타입 정밀도 조정
-- 기존: NUMERIC(5, 2) -> 최대 999.99
-- 변경: NUMERIC(10, 2) -> 최대 99999999.99
ALTER TABLE products
    ALTER COLUMN price TYPE NUMERIC(10, 2);

-- 오버플로우 발생 가능 집계 쿼리 처리
SELECT
    SUM(amount::BIGINT) AS total_amount  -- INTEGER -> BIGINT로 업캐스트
FROM transactions;

원인 3: 문자열 길이 초과 해결

-- 문제 발생 예시
CREATE TABLE customers (
    name VARCHAR(10)
);

INSERT INTO customers VALUES ('이것은매우긴이름입니다');
-- ERROR: value too long for type character varying(10)

-- 해결 방법 1: 컬럼 길이 확장
ALTER TABLE customers ALTER COLUMN name TYPE VARCHAR(100);

-- 해결 방법 2: 삽입 시 자동 트런케이션 (데이터 손실 주의)
INSERT INTO customers (name)
VALUES (LEFT('이것은매우긴이름입니다', 10));

-- 해결 방법 3: 길이 초과 데이터 사전 탐지 쿼리
SELECT
    id,
    name,
    LENGTH(name) AS name_length
FROM staging_customers
WHERE LENGTH(name) > 10
ORDER BY name_length DESC;

-- 해결 방법 4: CHECK 제약 대신 TRIGGER로 유연한 처리
CREATE OR REPLACE FUNCTION truncate_name()
RETURNS TRIGGER AS $$
BEGIN
    NEW.name := LEFT(NEW.name, 10);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_truncate_name
BEFORE INSERT OR UPDATE ON customers
FOR EACH ROW EXECUTE FUNCTION truncate_name();

예방 방법

1. 입력 데이터 검증 레이어 구축 (애플리케이션 + DB 이중 검증)

애플리케이션 레이어에서 1차 검증을 수행하되, DB 레벨에서도 CHECK 제약 조건, 도메인 타입, 트리거를 통한 2차 방어선을 반드시 구성하십시오. 특히 ETL 파이프라인이나 외부 API로부터 데이터를 수신할 때는 스테이징 테이블에 먼저 TEXT 타입으로 적재한 뒤, 검증 쿼리를 통과한 데이터만 운영 테이블로 이관하는 ELT 패턴을 적극 활용하는 것을 권장합니다.

-- 스테이징 → 운영 테이블 이관 시 검증 패턴
INSERT INTO orders (order_id, customer_id, amount)
SELECT
    order_id::INTEGER,
    customer_id::INTEGER,
    amount::NUMERIC(12, 2)
FROM staging_orders
WHERE order_id ~ '^[0-9]+$'
  AND customer_id ~ '^[0-9]+$'
  AND amount ~ '^\d+(\.\d{1,2})?$'
  AND amount::NUMERIC(12, 2) > 0;

2. 에러 핸들링 및 모니터링 강화

pg_stat_activity, log_min_error_statement, 그리고 postgresql.conf의 로깅 설정을 통해 22xxx 클래스 에러를 실시간으로 감지하고 알림을 받을 수 있는 체계를 마련하십시오. 또한 PL/pgSQL 프로시저 내에서는 반드시 EXCEPTION WHEN data_exception THEN 블록을 작성하여 에러 발생 시 상세 로그를 남기고 롤백이 올바르게 수행되도록 구현해야 합니다.

-- 에러 로깅 테이블 생성
CREATE TABLE IF NOT EXISTS error_log (
    id BIGSERIAL PRIMARY KEY,
    error_code TEXT,
    error_message TEXT,
    context_info TEXT,
    occurred_at TIMESTAMPTZ DEFAULT NOW()
);

-- 에러 핸들링 프로시저 예시
CREATE OR REPLACE PROCEDURE safe_insert_order(
    p_order_id TEXT,
    p_amount TEXT
)
LANGUAGE plpgsql AS $$
BEGIN
    INSERT INTO orders (order_id, amount)
    VALUES (p_order_id::INTEGER, p_amount::NUMERIC(12, 2));

EXCEPTION
    WHEN data_exception THEN
        INSERT INTO error_log (error_code, error_message, context_info)
        VALUES (
            SQLSTATE,
            SQLERRM,
            FORMAT('order_id=%s, amount=%s', p_order_id, p_amount)
        );
        RAISE WARNING '데이터 예외 발생: % (order_id: %)', SQLERRM, p_order_id;
END;
$$;

관련 에러

| 에러 코드 | 이름 | 설명 |

|———–|——|——|

| 22001 | string_data_right_truncation | 문자열이 컬럼 최대 길이를 초과 |

| 22003 | numeric_value_out_of_range | 숫자 값이 타입 허용 범위 초과 |

| 22007 | invalid_datetime_format | 잘못된 날짜/시간 형식 |

| 22008 | datetime_field_overflow | 날짜/시간 필드 값 범위 초과 |

| 22012 | division_by_zero | 0으로 나누기 시도 |

| 22018 | invalid_character_value_for_cast | 캐스트 불가능한 문자 값 |

| 22P02 | invalid_text_representation | 텍스트를 타입으로 파싱 실패 |

22000은 위 에러들의 부모 클래스이므로, EXCEPTION WHEN data_exception THEN 구문 하나로 22xxx 계열 에러 전체를 포착할 수 있다는 점을 기억하십시오. 실무에서는 더 구체적인 하위 에러를 먼저 처리하고, 마지막에 data_exception으로 나머지를 잡는 계층적 예외 처리 패턴을 권장합니다.


DBMS 에러 코드 시리즈

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

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

댓글 남기기