2026년 06월 14일 | DBMS Error 가이드
이 글에서 다루는 내용
22P01 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22P01 floating point exception 는?
PostgreSQL 에러 코드 22P01은 부동소수점 연산 중 예외적인 상황이 발생했을 때 나타나는 오류입니다. 이 에러는 주로 0으로 나누기, 무한대(Infinity), 또는 NaN(Not a Number) 값이 연산에 포함될 때 발생합니다. 특히 재무 계산, 통계 처리, 과학적 데이터 분석과 같이 부동소수점 연산이 빈번한 환경에서 자주 마주치게 됩니다.
주요 발생 원인
- 부동소수점 0으로 나누기 (Division by Zero)
정수형과 달리 float 또는 double precision 타입에서 0으로 나누기를 수행하면 PostgreSQL은 단순한 division_by_zero 에러 대신 22P01 floating point exception을 발생시킬 수 있습니다. 예를 들어, 1.0::float / 0.0::float 연산은 IEEE 754 표준에 따라 Infinity를 반환하거나 예외를 발생시킵니다. 이는 실무에서 집계 함수나 비율 계산 쿼리에서 분모가 0이 되는 상황에 가장 흔하게 발생합니다.
- NaN(Not a Number) 값의 연산 참여
데이터 파이프라인이나 외부 시스템으로부터 유입된 데이터에 NaN이 포함되어 있을 때, 해당 값이 수치 연산에 포함되면 예상치 못한 예외가 발생할 수 있습니다. PostgreSQL의 float 타입은 'NaN'::float을 허용하지만, 이를 일반 수치 연산(비교, 사칙연산 등)에 활용할 경우 결과가 불명확하거나 예외가 발생합니다. 특히 ORDER BY, GROUP BY, 또는 인덱스 연산에서 NaN이 혼재될 경우 예측하기 어려운 동작이 나타납니다.
- Infinity 값의 연산 충돌
'Infinity'::float 또는 '-Infinity'::float 값이 데이터에 포함된 상태에서 빼기, 곱하기, 나누기 등의 연산을 수행하면 수학적으로 정의되지 않는 결과(예: Infinity - Infinity = NaN)가 생성될 수 있습니다. 이런 상황은 센서 데이터, 로그 데이터, 또는 외부 API로부터 수신된 값에서 유효성 검사 없이 바로 연산을 수행할 때 빈번하게 발생합니다. 결과적으로 연산 결과가 NaN이 되거나 부동소수점 예외를 유발합니다.
해결 방법
원인 1: 부동소수점 0으로 나누기 방어
NULLIF 함수를 사용하여 분모가 0이 되는 상황을 사전에 방지하는 것이 가장 실용적인 해결책입니다.
-- 문제가 되는 쿼리 예시
SELECT revenue::float / visits::float AS conversion_rate
FROM campaign_stats;
-- 해결: NULLIF로 분모 0 방어
SELECT
campaign_id,
revenue,
visits,
revenue::float / NULLIF(visits, 0)::float AS conversion_rate
FROM campaign_stats;
-- 추가: CASE WHEN으로 더 명확하게 처리
SELECT
campaign_id,
CASE
WHEN visits = 0 THEN NULL
WHEN visits IS NULL THEN NULL
ELSE revenue::float / visits::float
END AS conversion_rate
FROM campaign_stats;
원인 2: NaN 값 필터링 및 처리
-- 테이블 내 NaN 값 확인
SELECT *
FROM sensor_data
WHERE value::text = 'NaN'
OR value != value; -- NaN은 자기 자신과 같지 않은 유일한 값
-- NaN 값을 NULL로 대체하여 안전하게 처리
UPDATE sensor_data
SET value = NULL
WHERE value != value; -- NaN 감지 트릭
-- 쿼리 수준에서 NaN 필터링
SELECT
sensor_id,
AVG(CASE WHEN value = value THEN value ELSE NULL END) AS avg_value
FROM sensor_data
GROUP BY sensor_id;
-- 또는 FILTER 절 활용 (PostgreSQL 9.4+)
SELECT
sensor_id,
AVG(value) FILTER (WHERE value = value AND value IS NOT NULL) AS safe_avg
FROM sensor_data
GROUP BY sensor_id;
원인 3: Infinity 값 처리
-- Infinity 값 확인
SELECT *
FROM financial_data
WHERE value = 'Infinity'::float
OR value = '-Infinity'::float;
-- Infinity와 NaN을 동시에 처리하는 안전한 함수 생성
CREATE OR REPLACE FUNCTION safe_float(val float)
RETURNS float AS $$
BEGIN
IF val IS NULL THEN
RETURN NULL;
ELSIF val != val THEN -- NaN 체크
RETURN NULL;
ELSIF val = 'Infinity'::float OR val = '-Infinity'::float THEN
RETURN NULL;
ELSE
RETURN val;
END IF;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 함수 활용 예시
SELECT
id,
safe_float(measurement) AS safe_measurement,
safe_float(measurement_a) + safe_float(measurement_b) AS safe_sum
FROM raw_measurements;
-- CHECK 제약조건으로 Infinity 유입 원천 차단
ALTER TABLE financial_data
ADD CONSTRAINT chk_no_infinity
CHECK (
amount IS NULL
OR (amount != 'Infinity'::float
AND amount != '-Infinity'::float
AND amount = amount) -- NaN도 동시에 차단
);
예방 방법
- 입력 데이터 유효성 검사 강화 (CHECK 제약조건 + 트리거 활용)
부동소수점 컬럼이 있는 테이블에는 반드시 NaN과 Infinity를 차단하는 CHECK 제약조건을 설정하세요. 외부 시스템에서 데이터가 유입되는 ETL 파이프라인이나 API 수신 테이블에는 트리거를 통해 삽입/수정 시점에 유효성을 검증하는 것이 가장 효과적입니다.
“`sql
— 재사용 가능한 도메인 타입 정의
CREATE DOMAIN safe_float AS float
CHECK (
VALUE IS NULL
OR (VALUE = VALUE — NaN 차단
AND VALUE != ‘Infinity’::float
AND VALUE != ‘-Infinity’::float)
);
— 도메인 타입을 컬럼에 적용
CREATE TABLE measurements (
id SERIAL PRIMARY KEY,
sensor_id INT NOT NULL,
value safe_float, — 안전한 float 도메인 사용
recorded_at TIMESTAMPTZ DEFAULT NOW()
);
“`
- 정기적인 데이터 품질 모니터링 쿼리 운영
운영 환경에서는 주기적으로 부동소수점 컬럼의 이상값을 스캔하는 모니터링 쿼리를 스케줄러(pg_cron 등)로 실행하여 문제를 조기에 감지하세요.
“`sql
— 데이터 품질 점검 쿼리 예시 (pg_cron 또는 외부 스케줄러로 주기 실행)
SELECT
schemaname,
tablename,
attname AS column_name,
‘NaN or Infinity detected’ AS issue
FROM pg_stats
WHERE n_distinct < 0 -- 통계 이상 징후 확인
— 실제 테이블 점검 예시
UNION ALL
SELECT
‘public’,
‘sensor_data’,
‘value’,
COUNT(*)::text || ‘ problematic rows’
FROM sensor_data
WHERE value != value — NaN
OR value = ‘Infinity’::float
OR value = ‘-Infinity’::float;
“`
관련 에러
- 22012 (division_by_zero): 정수형 나누기에서 0으로 나눌 때 발생하는 에러로, 22P01과 혼동될 수 있습니다. 정수 타입에서는 22012, 부동소수점 타입에서는 22P01이 발생하는 경향이 있으므로 컬럼 타입 확인이 중요합니다.
- 22003 (numeric_value_out_of_range):
numeric또는integer타입에서 허용 범위를 초과하는 값이 연산될 때 발생합니다. 부동소수점 예외와 달리 주로 오버플로우 상황에서 나타납니다.
- 22000 (data_exception): 데이터 관련 예외의 최상위 카테고리 코드로, 22P01을 포함한 다양한 데이터 예외의 부모 에러 클래스입니다. 예외 처리 코드 작성 시 상위 클래스로 포괄적으로 잡을 때 활용됩니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.