2026년 08월 18일 | DBMS Error 가이드
이 글에서 다루는 내용
22P01 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22P01 floating point exception 란?
PostgreSQL 에러 코드 22P01은 부동소수점 예외(Floating Point Exception) 를 의미하며, 연산 중 수학적으로 정의되지 않거나 불가능한 부동소수점 계산이 발생했을 때 나타납니다. 대표적인 사례로는 0으로 나누기, 무한대(Infinity) 연산, NaN(Not a Number) 값이 연산에 개입되는 경우가 있습니다. 이 에러는 주로 float4, float8, numeric 타입의 컬럼을 다루는 집계 함수, 수식 연산, 또는 외부 데이터 임포트 과정에서 빈번하게 발생합니다.
주요 발생 원인
1. 부동소수점 값의 0으로 나누기 (Division by Zero with Float)
정수형 나누기에서 발생하는 22012 division_by_zero와는 달리, 부동소수점 연산에서는 동작이 미묘하게 다릅니다. float8 또는 float4 타입에서 특정 연산이 IEEE 754 표준에 따라 Infinity 또는 NaN을 반환하지만, PostgreSQL의 엄격한 설정이나 특정 함수에서는 이를 허용하지 않고 22P01 예외를 발생시킵니다. 특히 집계 함수나 윈도우 함수 내부에서 중간 계산 결과가 NaN 또는 Infinity로 변환될 때 예외가 트리거됩니다.
2. NaN 또는 Infinity 값이 포함된 데이터 연산
외부 시스템(예: Python, R, CSV 파일)에서 데이터를 임포트할 때 NaN, Infinity, -Infinity 같은 특수 값이 그대로 float 컬럼에 저장되는 경우가 있습니다. 이러한 값들이 SUM(), AVG(), EXP(), LOG(), SQRT() 등의 수학 함수와 만나면 연산 결과가 정의되지 않아 22P01 에러가 발생합니다. 특히 SQRT(-1.0) 처럼 음수에 대한 제곱근 연산이나, LOG(0.0)과 같이 수학적으로 불가능한 연산이 이 에러의 주요 트리거입니다.
3. 집계 함수 및 통계 함수에서의 오버플로우 또는 언더플로우
float8 타입의 최대값(1.7976931348623157E+308)을 초과하는 연산이나, 표현 가능한 최솟값보다 작은 값이 계산될 때 오버플로우 혹은 언더플로우가 발생합니다. 예를 들어 매우 큰 수를 EXP() 함수에 넣거나, 대규모 데이터셋의 SUM() 결과가 float8의 표현 범위를 초과할 경우 이 예외가 발생할 수 있습니다. 이는 금융 데이터, 과학 계산, 머신러닝 피처 엔지니어링 등 수치 연산이 집중된 도메인에서 특히 자주 발생합니다.
해결 방법
원인 1 해결: 0으로 나누기 방어 처리
NULLIF 또는 CASE WHEN을 활용하여 분모가 0인 경우를 사전에 처리합니다.
-- 문제가 되는 쿼리 예시
SELECT revenue / cost AS profit_ratio
FROM sales_data;
-- 해결책 1: NULLIF를 사용하여 분모가 0일 때 NULL 반환
SELECT revenue / NULLIF(cost, 0.0) AS profit_ratio
FROM sales_data;
-- 해결책 2: CASE WHEN으로 명시적 처리
SELECT
CASE
WHEN cost = 0.0 OR cost IS NULL THEN NULL
ELSE revenue / cost
END AS profit_ratio
FROM sales_data;
-- 해결책 3: 결과에서 Infinity와 NaN 필터링
SELECT revenue / NULLIF(cost, 0.0) AS profit_ratio
FROM sales_data
WHERE cost != 0.0
AND cost IS NOT NULL
AND revenue = revenue -- NaN 필터링 (NaN != NaN)
AND revenue != 'Infinity'::float8
AND revenue != '-Infinity'::float8;
원인 2 해결: NaN / Infinity 값 탐지 및 정제
데이터를 처리하기 전에 특수 부동소수점 값을 필터링하거나 대체합니다.
-- NaN, Infinity 값이 있는 행 탐지
SELECT id, value_col
FROM measurement_table
WHERE value_col IS DISTINCT FROM value_col -- NaN 탐지 (NaN != NaN)
OR value_col = 'Infinity'::float8
OR value_col = '-Infinity'::float8;
-- NaN 값을 NULL 또는 기본값으로 교체
UPDATE measurement_table
SET value_col = NULL
WHERE value_col IS DISTINCT FROM value_col; -- NaN 조건
-- Infinity 값을 컬럼의 최대 정상값으로 교체
UPDATE measurement_table
SET value_col = (
SELECT MAX(v) FROM measurement_table m2
WHERE m2.value_col != 'Infinity'::float8
AND m2.value_col != '-Infinity'::float8
AND m2.value_col = m2.value_col -- NaN 제외
)
WHERE value_col = 'Infinity'::float8;
-- 수학 함수 사용 전 방어적 처리 예시
SELECT
id,
CASE
WHEN value_col IS NULL THEN NULL
WHEN value_col IS DISTINCT FROM value_col THEN NULL -- NaN 처리
WHEN value_col < 0 THEN NULL -- SQRT 음수 방지
ELSE SQRT(value_col)
END AS sqrt_value,
CASE
WHEN value_col IS NULL THEN NULL
WHEN value_col <= 0 THEN NULL -- LOG 0 이하 방지
ELSE LOG(value_col)
END AS log_value
FROM measurement_table;
원인 3 해결: 오버플로우 방지 및 타입 변환
float8 범위를 초과하는 연산 전에 numeric 타입으로 캐스팅하거나 값의 범위를 검증합니다.
-- EXP() 오버플로우 방지
SELECT
id,
CASE
WHEN exponent_col > 700 THEN 'Overflow' -- EXP(709) 이 float8 최대에 근접
WHEN exponent_col < -700 THEN 'Underflow'
ELSE EXP(exponent_col)::text
END AS safe_exp_result
FROM calculation_table;
-- SUM 오버플로우 방지: numeric 타입으로 변환 후 집계
SELECT
category,
SUM(large_float_col::numeric) AS total_sum, -- numeric으로 캐스팅
AVG(large_float_col::numeric) AS avg_value
FROM big_data_table
GROUP BY category;
-- 범위 검증 함수 생성으로 재사용성 확보
CREATE OR REPLACE FUNCTION safe_float_exp(val float8)
RETURNS float8 AS $$
BEGIN
IF val > 709 THEN
RETURN 'Infinity'::float8;
ELSIF val < -745 THEN
RETURN 0.0;
ELSE
RETURN EXP(val);
END IF;
EXCEPTION
WHEN floating_point_exception THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 함수 사용 예시
SELECT id, safe_float_exp(exponent_col) AS safe_result
FROM calculation_table;
예방 방법
1. CHECK 제약조건으로 특수 부동소수점 값 입력 차단
데이터가 테이블에 저장되는 시점에 NaN, Infinity 값이 들어오지 못하도록 DB 레벨에서 제약조건을 설정합니다. 이 방법은 애플리케이션 레이어의 검증에 의존하지 않고 데이터 무결성을 보장하는 가장 확실한 방법입니다.
-- float 컬럼에 NaN, Infinity 입력 방지 CHECK 제약
ALTER TABLE measurement_table
ADD CONSTRAINT chk_value_col_valid
CHECK (
value_col IS NULL
OR (
value_col = value_col -- NaN 방지 (NaN != NaN이 True이면 실패)
AND value_col != 'Infinity'::float8
AND value_col != '-Infinity'::float8
AND value_col > -1e308 -- 극단적 언더플로우 방지
AND value_col < 1e308 -- 극단적 오버플로우 방지
)
);
-- 입력 테스트
INSERT INTO measurement_table(value_col) VALUES (42.5); -- 성공
INSERT INTO measurement_table(value_col) VALUES ('NaN'); -- 실패 (제약조건 위반)
INSERT INTO measurement_table(value_col) VALUES ('Infinity'); -- 실패
2. 방어적 뷰(View) 또는 래퍼 함수 생성으로 안전한 수학 연산 레이어 구축
자주 사용하는 수학 함수에 대해 안전한 래퍼를 만들고 팀 전체가 이를 통해 연산하도록 표준화합니다. 이렇게 하면 개별 쿼리마다 CASE WHEN을 반복 작성할 필요가 없어 코드 품질과 안전성을 동시에 높일 수 있습니다.
-- 안전한 SQRT 래퍼 함수
CREATE OR REPLACE FUNCTION safe_sqrt(val float8)
RETURNS float8 AS $$
BEGIN
IF val IS NULL OR val IS DISTINCT FROM val THEN -- NULL 또는 NaN
RETURN NULL;
ELSIF val < 0 THEN
RETURN NULL; -- 또는 도메인에 따라 0 반환 가능
ELSE
RETURN SQRT(val);
END IF;
EXCEPTION
WHEN floating_point_exception THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT;
-- 안전한 나누기 래퍼 함수
CREATE OR REPLACE FUNCTION safe_divide(numerator float8, denominator float8)
RETURNS float8 AS $$
BEGIN
IF denominator IS NULL OR denominator = 0.0 THEN
RETURN NULL;
ELSIF numerator IS DISTINCT FROM numerator THEN -- numerator가 NaN
RETURN NULL;
ELSE
RETURN numerator / denominator;
END IF;
EXCEPTION
WHEN floating_point_exception THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 팀 공통 안전 연산 뷰 예시
CREATE OR REPLACE VIEW v_safe_metrics AS
SELECT
id,
safe_sqrt(variance_col) AS std_deviation,
safe_divide(revenue_col, cost_col) AS profit_ratio,
safe_float_exp(growth_rate_col) AS growth_multiplier
FROM metrics_raw_table;
관련 에러
22012–division_by_zero: 정수형 또는 numeric 타입에서 0으로 나누기 시 발생.22P01이 float 특화라면,22012는 정수 나누기 전용입니다.22003–numeric_value_out_of_range: numeric 또는 integer 타입이 허용 범위를 초과했을 때 발생하며, float의 오버플로우 상황과 유사한 맥락에서 나타납니다.22023–invalid_parameter_value: 수학 함수에 유효하지 않은 인자를 전달했을 때(예:LOG(-1)) 발생할 수 있으며,22P01과 혼동되는 경우가 있습니다.22026–string_data_length_mismatch: 데이터 임포트 과정에서 연관되어 나타날 수 있으며, 특수 부동소수점 값이 잘못된 문자열로 임포트될 때 함께 발생하기도 합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.