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

22012
2026년 10월 12일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22012 division by zero 는?

PostgreSQL 에러 코드 22012, division by zero는 말 그대로 숫자를 0으로 나누려 할 때 발생하는 에러입니다. SQL 연산 중 나눗셈(/) 또는 모듈로(%) 연산의 분모(제수)가 0이 되는 순간 PostgreSQL은 즉시 트랜잭션을 중단하고 이 에러를 반환합니다. 실무에서는 집계 함수, 비율 계산, 통계 쿼리 등에서 예상치 못한 데이터가 유입될 때 빈번하게 발생하며, 배치 작업이나 리포팅 쿼리에서 특히 치명적인 장애를 유발할 수 있습니다.

주요 발생 원인

  • 집계 결과값이 0인 경우 분모로 사용

가장 흔한 원인입니다. COUNT(), SUM() 등 집계 함수의 결과를 분모로 사용할 때, 해당 그룹의 데이터가 존재하지 않거나 모든 값이 0이면 분모가 0이 됩니다. 예를 들어, 부서별 평균 급여를 전체 합계로 나눠 비율을 계산하는 쿼리에서 특정 부서의 데이터가 없을 경우 이 에러가 발생합니다.

  • 사용자 입력 또는 외부 데이터의 유효성 검증 누락

애플리케이션 레이어에서 충분한 유효성 검증 없이 사용자 입력값이나 외부 시스템에서 수신된 데이터를 그대로 분모에 사용할 경우 발생합니다. API를 통해 수신된 수량(quantity), 단가(unit price), 기간(duration) 등의 값이 0으로 들어올 수 있으며, 특히 레거시 시스템 연동이나 데이터 마이그레이션 과정에서 자주 발생합니다.

  • 동적 쿼리 및 복잡한 조인에서 예상치 못한 NULL 또는 0 값 발생

복잡한 다중 조인이나 서브쿼리에서 특정 조건에 의해 연산 중간에 0이 생성되는 경우입니다. CASE WHEN 조건이 의도치 않게 0을 반환하거나, LEFT JOIN 결과로 생성된 NULL이 산술 연산에서 0으로 처리되는 상황을 개발자가 미처 고려하지 못하는 경우가 많습니다.

해결 방법

원인 1 해결: NULLIF()를 활용한 0 제거

NULLIF(expr, 0)을 사용하면 분모가 0일 때 NULL을 반환하여 에러 대신 NULL 결과를 얻을 수 있습니다.

-- 문제가 되는 쿼리
SELECT department_id,
       SUM(salary) / COUNT(*) AS avg_salary_ratio
FROM employees
GROUP BY department_id;

-- NULLIF로 해결
SELECT department_id,
       SUM(salary) / NULLIF(COUNT(*), 0) AS avg_salary_ratio
FROM employees
GROUP BY department_id;

-- 부서별 매출 비율 계산 (실무 예제)
SELECT
    department_id,
    department_sales,
    total_sales,
    ROUND(
        (department_sales::NUMERIC / NULLIF(total_sales, 0)) * 100,
        2
    ) AS sales_ratio_pct
FROM (
    SELECT
        department_id,
        SUM(amount) AS department_sales,
        SUM(SUM(amount)) OVER () AS total_sales
    FROM orders
    GROUP BY department_id
) sub;

원인 2 해결: CASE WHEN으로 명시적 조건 처리

입력값의 유효성을 SQL 내부에서도 방어적으로 처리합니다.

-- CASE WHEN을 이용한 방어적 나눗셈
SELECT
    order_id,
    total_price,
    quantity,
    CASE
        WHEN quantity = 0 OR quantity IS NULL THEN NULL
        ELSE total_price / quantity
    END AS unit_price
FROM orders;

-- 함수로 만들어 재사용
CREATE OR REPLACE FUNCTION safe_divide(
    numerator   NUMERIC,
    denominator NUMERIC,
    fallback    NUMERIC DEFAULT NULL
)
RETURNS NUMERIC AS $$
BEGIN
    IF denominator IS NULL OR denominator = 0 THEN
        RETURN fallback;
    END IF;
    RETURN numerator / denominator;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 사용 예
SELECT
    product_id,
    safe_divide(revenue, units_sold, 0) AS avg_revenue_per_unit
FROM product_stats;

원인 3 해결: 복잡한 쿼리에서의 방어 처리

-- LEFT JOIN 후 집계 나눗셈 시 안전 처리
SELECT
    c.customer_id,
    c.customer_name,
    COALESCE(SUM(o.amount), 0) AS total_amount,
    COUNT(o.order_id) AS order_count,
    COALESCE(
        SUM(o.amount) / NULLIF(COUNT(o.order_id), 0),
        0
    ) AS avg_order_amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name;

-- 통계 쿼리에서의 표준편차 분모 처리
SELECT
    category,
    COUNT(*) AS cnt,
    AVG(price) AS avg_price,
    CASE
        WHEN COUNT(*) <= 1 THEN 0
        ELSE STDDEV(price)
    END AS price_stddev,
    CASE
        WHEN AVG(price) = 0 THEN NULL
        ELSE STDDEV(price) / NULLIF(AVG(price), 0) * 100
    END AS coefficient_of_variation
FROM products
GROUP BY category;

예방 방법

  • 데이터베이스 레벨의 CHECK 제약 조건 적용

분모로 사용될 컬럼에 CHECK 제약 조건을 걸어 0이 입력되지 못하도록 원천 차단합니다. 이는 애플리케이션 버그나 직접 SQL 실행으로 인한 잘못된 데이터 유입을 막는 가장 근본적인 방법입니다.

“`sql

— 테이블 생성 시 제약 조건 추가

CREATE TABLE order_items (

item_id SERIAL PRIMARY KEY,

order_id INT NOT NULL,

quantity INT NOT NULL CHECK (quantity > 0),

unit_price NUMERIC(10, 2) NOT NULL CHECK (unit_price >= 0),

duration_days INT CHECK (duration_days IS NULL OR duration_days > 0)

);

— 기존 테이블에 제약 조건 추가

ALTER TABLE products

ADD CONSTRAINT chk_stock_positive CHECK (stock_quantity >= 0);

“`

  • 공통 안전 나눗셈 함수 표준화 및 코드 리뷰 정책 수립

팀 내에서 나눗셈 연산이 포함된 SQL에는 반드시 NULLIF() 또는 safe_divide() 함수를 사용하도록 코딩 컨벤션을 정하고, PR(Pull Request) 코드 리뷰 시 단순 / 연산자 사용을 체크리스트 항목으로 등록합니다. 또한 개발/스테이징 환경에서 경계값(0, NULL, 음수) 테스트 케이스를 반드시 포함하도록 QA 프로세스를 수립합니다.

“`sql

— 뷰(View)를 통한 안전한 계산 레이어 제공

CREATE OR REPLACE VIEW v_product_performance AS

SELECT

p.product_id,

p.product_name,

COALESCE(s.total_sold, 0) AS total_sold,

COALESCE(s.total_revenue, 0) AS total_revenue,

safe_divide(s.total_revenue, s.total_sold) AS avg_selling_price,

safe_divide(s.total_revenue, t.total_revenue_all) * 100 AS revenue_share_pct

FROM products p

LEFT JOIN product_sales_summary s ON p.product_id = s.product_id

CROSS JOIN (SELECT SUM(total_revenue) AS total_revenue_all FROM product_sales_summary) t;

“`

관련 에러

  • 22003 numeric_value_out_of_range: 나눗셈 결과값이 해당 컬럼의 데이터 타입 범위를 초과할 때 발생하며, division by zero 에러를 방어한 후에도 결과값 범위 검증이 필요합니다.
  • 22P02 invalid_text_representation: 문자열을 숫자로 변환(CAST)하는 과정에서 유효하지 않은 값이 있을 때 발생하며, 동적 데이터를 분모로 변환할 때 함께 고려해야 합니다.
  • 42883 undefined_function: safe_divide 같은 사용자 정의 함수를 다른 환경에 배포하지 않았을 때 발생할 수 있으므로, 함수 기반 해결책 도입 시 배포 스크립트 관리가 중요합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기