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

22P01
2026년 10월 22일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22P01 floating point exception 는?

PostgreSQL 에러 코드 22P01은 부동소수점 예외(Floating Point Exception) 를 의미하며, 수치 연산 과정에서 IEEE 754 부동소수점 표준이 허용하지 않는 연산이 발생했을 때 트리거됩니다. 대표적인 상황으로는 0으로 나누기, 무한대(infinity) 연산, NaN(Not a Number) 값이 예기치 않게 발생하는 경우 등이 있습니다. 이 에러는 데이터 분석, 통계 연산, 또는 대규모 집계 쿼리를 실행할 때 특히 자주 마주치게 되며, 잘못된 입력 데이터나 설계 오류에서 비롯되는 경우가 많습니다.


주요 발생 원인

1. 0으로 나누기 (Division by Zero)

가장 흔한 원인 중 하나로, FLOAT 또는 DOUBLE PRECISION 타입의 컬럼을 나눗셈 연산에 사용할 때 분모가 0이 되는 경우 발생합니다. 정수형(INTEGER)의 경우 22012 division_by_zero 에러가 발생하지만, 부동소수점 타입에서는 22P01이 발생하거나 Infinity, NaN 같은 특수 값을 반환하는 방식으로 동작이 달라질 수 있습니다. 실무에서 비율 계산, 평균 단가 계산 등 분모가 동적으로 결정되는 쿼리에서 자주 발생합니다.

2. NaN 또는 Infinity 값이 포함된 데이터와의 연산

외부 시스템(Python, R, 데이터 파이프라인 등)으로부터 유입된 데이터에 NaN 또는 Infinity 값이 포함되어 있을 때, 이 값들과의 추가 연산이 예외를 유발할 수 있습니다. PostgreSQL은 FLOAT 타입에서 NaN과 Infinity를 저장할 수 있도록 허용하지만, 특정 수학 함수(sqrt, log, exp 등)에 이 값들이 입력될 경우 부동소수점 예외가 발생합니다. 데이터 품질 검증 없이 원시 데이터를 바로 연산에 사용하는 파이프라인 환경에서 특히 위험합니다.

3. 범위를 초과하는 수치 연산 (Overflow/Underflow)

FLOAT 또는 DOUBLE PRECISION 타입이 표현할 수 있는 최댓값(1.7976931348623157e+308)을 초과하거나 최솟값에 미치지 못하는 연산이 수행될 때 발생합니다. 예를 들어, 지수 연산(exp())에 매우 큰 값을 입력하거나 매우 작은 양수 값이 언더플로우를 일으키는 경우입니다. 금융 도메인이나 과학 계산 도메인에서 큰 수를 다루는 쿼리에서 종종 발생하며, 예측하기 어렵기 때문에 사전 검증이 필수적입니다.


해결 방법

원인 1 해결: 0으로 나누기 방지

NULLIF() 함수를 사용하여 분모가 0일 경우 NULL로 대체하면 예외 발생 없이 안전하게 처리할 수 있습니다.

-- 문제가 되는 쿼리 예시
SELECT revenue / cost AS profit_ratio
FROM financial_data;

-- 해결책 1: NULLIF로 분모가 0인 경우 NULL 반환
SELECT revenue / NULLIF(cost, 0) AS profit_ratio
FROM financial_data;

-- 해결책 2: CASE WHEN으로 명시적 처리
SELECT 
    CASE 
        WHEN cost = 0 OR cost IS NULL THEN NULL
        ELSE revenue / cost 
    END AS profit_ratio
FROM financial_data;

-- 해결책 3: COALESCE와 함께 기본값 지정
SELECT 
    COALESCE(revenue / NULLIF(cost, 0), 0) AS profit_ratio
FROM financial_data;

원인 2 해결: NaN / Infinity 값 필터링 및 처리

데이터를 연산에 사용하기 전에 NaN과 Infinity 여부를 확인하고 정제하는 과정이 필요합니다.

-- NaN, Infinity 값이 포함된 데이터 확인
SELECT *
FROM sensor_data
WHERE value = 'NaN'::float
   OR value = 'Infinity'::float
   OR value = '-Infinity'::float;

-- NaN 및 Infinity를 NULL로 대체한 후 연산
SELECT 
    sensor_id,
    AVG(
        CASE 
            WHEN value = 'NaN'::float 
              OR value = 'Infinity'::float 
              OR value = '-Infinity'::float 
            THEN NULL
            ELSE value
        END
    ) AS avg_value
FROM sensor_data
GROUP BY sensor_id;

-- 수학 함수 사용 전 안전한 입력 범위 확인
SELECT 
    id,
    CASE 
        WHEN value > 0 AND value != 'NaN'::float THEN sqrt(value)
        ELSE NULL
    END AS safe_sqrt
FROM measurements;

-- 데이터 정제 UPDATE 예시
UPDATE sensor_data
SET value = NULL
WHERE value = 'NaN'::float
   OR value = 'Infinity'::float
   OR value = '-Infinity'::float;

원인 3 해결: 오버플로우/언더플로우 방지

연산 전에 입력값의 범위를 검증하고, NUMERIC 타입으로 전환하는 방법을 고려해야 합니다.

-- 위험한 지수 연산 예시
SELECT exp(huge_number) FROM calculations; -- 오버플로우 발생 가능

-- 안전한 지수 연산: 범위 체크 후 처리
SELECT 
    id,
    CASE 
        WHEN exponent > 709 THEN NULL  -- exp()의 안전한 최대 입력값
        WHEN exponent < -745 THEN 0.0  -- 언더플로우 처리
        ELSE exp(exponent)
    END AS safe_exp_value
FROM calculations;

-- FLOAT 대신 NUMERIC 타입 사용 (정밀도 우선 시)
CREATE TABLE financial_transactions (
    id          SERIAL PRIMARY KEY,
    amount      NUMERIC(20, 6),   -- FLOAT 대신 NUMERIC 사용
    rate        NUMERIC(10, 8),
    calculated  NUMERIC(30, 10)
);

-- 기존 FLOAT 컬럼을 NUMERIC으로 변환
ALTER TABLE financial_data 
ALTER COLUMN price TYPE NUMERIC(15, 4) 
USING price::NUMERIC(15, 4);

예방 방법

1. 입력 데이터에 대한 CHECK 제약 조건 및 검증 함수 적용

테이블 설계 단계에서 CHECK 제약 조건을 걸어 NaN, Infinity, 또는 의도하지 않은 0 값이 저장되지 않도록 방지하는 것이 근본적인 예방책입니다. 또한, 데이터를 삽입하거나 업데이트하는 트리거 또는 함수 내부에 유효성 검사 로직을 추가하면 파이프라인 전반의 데이터 품질을 보장할 수 있습니다.

-- CHECK 제약 조건으로 이상값 방지
CREATE TABLE sensor_readings (
    id          SERIAL PRIMARY KEY,
    sensor_id   INTEGER NOT NULL,
    value       DOUBLE PRECISION NOT NULL,
    recorded_at TIMESTAMPTZ DEFAULT NOW(),
    -- NaN, Infinity 값 삽입 차단
    CONSTRAINT chk_valid_float 
        CHECK (value = value AND abs(value) != 'Infinity'::float)
);

-- 데이터 유효성 검사 함수
CREATE OR REPLACE FUNCTION is_valid_float(val DOUBLE PRECISION)
RETURNS BOOLEAN AS $$
BEGIN
    RETURN val = val  -- NaN 체크 (NaN != NaN은 TRUE)
       AND val != 'Infinity'::float
       AND val != '-Infinity'::float;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 활용 예시
SELECT * FROM sensor_data WHERE is_valid_float(value);

2. 연산 로직에 방어적 프로그래밍 패턴 적용 및 정기 데이터 감사

쿼리나 함수 내부에서 나눗셈, 제곱근, 로그 등의 연산을 수행하기 전에 항상 입력값의 유효성을 확인하는 방어적 패턴을 습관화해야 합니다. 더불어 주기적으로 테이블 내 NaN 및 Infinity 값의 존재 여부를 모니터링하는 감사 쿼리를 스케줄링하면 문제를 사전에 감지할 수 있습니다.

-- 정기 데이터 감사 쿼리 (pg_cron 등으로 스케줄링 가능)
SELECT 
    'sensor_data' AS table_name,
    COUNT(*) FILTER (WHERE value = 'NaN'::float) AS nan_count,
    COUNT(*) FILTER (WHERE value = 'Infinity'::float) AS pos_inf_count,
    COUNT(*) FILTER (WHERE value = '-Infinity'::float) AS neg_inf_count,
    COUNT(*) AS total_rows
FROM sensor_data;

관련 에러

  • 22012 (division_by_zero): 정수형 타입에서 0으로 나누기가 발생했을 때 나타나는 에러로, 22P01과 발생 원인이 유사하지만 데이터 타입이 다릅니다.
  • 22003 (numeric_value_out_of_range): NUMERIC 또는 INTEGER 타입에서 저장 가능한 범위를 초과하는 값이 발생했을 때 나타납니다. 22P01의 오버플로우 상황과 유사한 맥락에서 발생합니다.
  • 22026 (string_data_length_mismatch): 타입 변환 과정에서 발생할 수 있는 관련 에러로, 잘못된 형변환이 수치 예외로 이어지는 경우 함께 검토해야 합니다.
  • 22007 (invalid_datetime_format) / 22P02 (invalid_text_representation): 외부 데이터를 수치 타입으로 캐스팅할 때 유효하지 않은 문자열이 입력되어 발생하며, 부동소수점 예외와 동일한 데이터 품질 문제에서 비롯되는 경우가 많습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기