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 error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.