2026년 06월 04일 | DBMS Error 가이드
이 글에서 다루는 내용
22012 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22012 division by zero 는?
PostgreSQL 에러 코드 22012 (division by zero)는 SQL 연산 중 어떤 값을 0으로 나누려 할 때 발생하는 에러입니다. 수학적으로 0으로 나누는 연산은 정의되지 않기 때문에 PostgreSQL은 이를 허용하지 않고 즉시 트랜잭션을 중단시킵니다. 이 에러는 단순한 산술 연산뿐 아니라 집계 함수, 서브쿼리, 윈도우 함수 등 다양한 쿼리 컨텍스트에서도 발생할 수 있으며, 특히 데이터가 동적으로 변하는 프로덕션 환경에서 예기치 않게 나타나는 경우가 많습니다.
주요 발생 원인
- 직접적인 0 또는 0이 될 수 있는 컬럼으로 나누는 경우
가장 흔한 원인으로, 나눗셈 연산의 분모(denominator)가 명시적으로 0이거나, 특정 데이터 행에서 해당 컬럼 값이 0이 되는 경우입니다. 예를 들어, 매출 대비 비율을 계산할 때 일부 상품의 기준 수량이 0으로 저장되어 있다면 전체 쿼리가 실패하게 됩니다. 배치 작업이나 대용량 데이터 처리 시 중간에 에러가 발생하면 트랜잭션 전체가 롤백되므로 실무에서 매우 치명적일 수 있습니다.
- 집계 함수 결과나 서브쿼리 결과를 분모로 사용하는 경우
SUM(), COUNT(), AVG() 등의 집계 함수 결과를 분모로 사용할 때, 특정 그룹에 데이터가 없거나 모든 값이 상쇄되어 결과가 0이 되는 경우 에러가 발생합니다. 예를 들어 특정 기간에 판매 데이터가 전혀 없는 지점의 경우, SUM(sales)가 0을 반환하여 비율 계산 시 에러가 발생할 수 있습니다. 이러한 케이스는 개발 환경에서는 재현되지 않다가 프로덕션에서 특정 조건 하에 처음 발생하는 경우가 많아 더 위험합니다.
- 윈도우 함수 또는 동적 계산 컬럼을 분모로 사용하는 경우
ROW_NUMBER(), RANK(), LAG(), LEAD() 등 윈도우 함수의 결과나 CASE WHEN 등으로 동적으로 생성된 컬럼을 분모로 사용할 때도 예상치 못한 0 값이 분모에 들어올 수 있습니다. 특히 파티션 경계에서 값이 변하거나, 이전 행과의 차이를 계산하는 로직에서 두 값이 동일해 차이가 0이 되는 경우 에러가 발생합니다. 이런 유형의 버그는 쿼리 로직이 복잡할수록 추적하기 어렵습니다.
해결 방법
원인 1 해결: NULLIF() 또는 CASE WHEN으로 0 분모 처리
NULLIF(expr, 0)을 사용하면 분모가 0일 때 NULL을 반환하므로 나눗셈 결과도 NULL이 되어 에러를 방지할 수 있습니다. NULL로 처리된 값은 이후 COALESCE()로 기본값으로 치환하면 실무에서 깔끔하게 처리할 수 있습니다.
-- 문제가 되는 쿼리 (에러 발생)
SELECT
product_id,
revenue / quantity AS unit_price
FROM sales;
-- 해결책 1: NULLIF 사용 (0이면 NULL 반환, 에러 방지)
SELECT
product_id,
revenue / NULLIF(quantity, 0) AS unit_price
FROM sales;
-- 해결책 2: COALESCE와 결합하여 기본값 지정
SELECT
product_id,
COALESCE(revenue / NULLIF(quantity, 0), 0) AS unit_price
FROM sales;
-- 해결책 3: CASE WHEN으로 명시적 처리
SELECT
product_id,
CASE
WHEN quantity = 0 THEN NULL
ELSE revenue / quantity
END AS unit_price
FROM sales;
원인 2 해결: 집계 함수 결과를 분모로 사용할 때
-- 문제가 되는 쿼리 (특정 그룹의 SUM이 0일 때 에러)
SELECT
department_id,
individual_sales / SUM(total_sales) OVER (PARTITION BY department_id) AS sales_ratio
FROM employee_sales;
-- 해결책: NULLIF로 집계 결과 보호
SELECT
department_id,
employee_id,
COALESCE(
individual_sales / NULLIF(SUM(total_sales) OVER (PARTITION BY department_id), 0),
0
) AS sales_ratio
FROM employee_sales;
-- 서브쿼리 기반 집계의 경우
SELECT
a.store_id,
a.monthly_sales / NULLIF(b.total_target, 0) AS achievement_rate
FROM monthly_sales a
JOIN (
SELECT store_id, SUM(target) AS total_target
FROM sales_target
GROUP BY store_id
) b ON a.store_id = b.store_id;
원인 3 해결: 윈도우 함수와 동적 계산에서의 처리
-- 문제가 되는 쿼리 (이전 행과 값이 같을 때 분모가 0)
SELECT
date,
price,
(price - LAG(price) OVER (ORDER BY date)) / LAG(price) OVER (ORDER BY date) AS daily_return
FROM stock_prices;
-- 해결책: NULLIF로 LAG 결과 보호
SELECT
date,
price,
COALESCE(
(price - LAG(price) OVER (ORDER BY date))
/ NULLIF(LAG(price) OVER (ORDER BY date), 0),
0
) AS daily_return
FROM stock_prices;
-- 함수로 추상화하여 재사용성 높이기
CREATE OR REPLACE FUNCTION safe_divide(numerator NUMERIC, denominator NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
IF denominator = 0 OR denominator IS NULL THEN
RETURN NULL;
END IF;
RETURN numerator / denominator;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 함수 사용 예시
SELECT
product_id,
safe_divide(revenue, quantity) AS unit_price
FROM sales;
즉시 적용 가능한 디버깅 방법
-- 0이 되는 분모를 미리 찾아내는 진단 쿼리
SELECT
product_id,
quantity,
revenue
FROM sales
WHERE quantity = 0;
-- 실행 전 데이터 검증
DO $$
DECLARE
zero_count INTEGER;
BEGIN
SELECT COUNT(*) INTO zero_count
FROM sales
WHERE quantity = 0;
IF zero_count > 0 THEN
RAISE WARNING '% rows have quantity = 0. Division by zero risk detected.', zero_count;
END IF;
END;
$$;
예방 방법
- 테이블 레벨에서 CHECK 제약 조건으로 0 값 방지
분모로 사용되는 컬럼에 CHECK 제약 조건을 추가하여 데이터베이스 레벨에서 0 또는 음수 값이 입력되지 않도록 원천 차단합니다. 이는 애플리케이션 코드의 유효성 검사와 별개로 데이터 무결성을 보장하는 가장 확실한 방법입니다.
“`sql
— 테이블 생성 시 CHECK 제약 조건 추가
CREATE TABLE sales (
product_id INT NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
revenue NUMERIC(15, 2) NOT NULL
);
— 기존 테이블에 제약 조건 추가
ALTER TABLE sales
ADD CONSTRAINT chk_quantity_positive CHECK (quantity > 0);
“`
- 코드 리뷰 및 CI/CD 파이프라인에 나눗셈 패턴 린팅 규칙 추가
나눗셈 연산(/)이 포함된 모든 SQL에 대해 NULLIF 또는 CASE WHEN 패턴이 적용되었는지 코드 리뷰 체크리스트에 포함시킵니다. 더 나아가, sqlfluff나 자체 제작 린터를 CI/CD 파이프라인에 통합하여 / column_name 패턴이 NULLIF 없이 사용되면 경고 또는 에러로 처리하도록 자동화합니다. 이를 통해 개발 단계에서 잠재적 문제를 사전에 차단할 수 있습니다.
관련 에러
- 22003 (numeric_value_out_of_range): 나눗셈 결과가 해당 데이터 타입의 범위를 초과할 때 발생하며,
division by zero와 함께 수치 연산 관련 에러로 자주 언급됩니다. - 22P02 (invalid_text_representation): 문자열을 숫자로 캐스팅하는 과정에서 실패할 때 발생하며, 동적 쿼리에서 분모 값을 문자열로부터 변환할 때 연쇄적으로 발생할 수 있습니다.
- 23514 (check_violation): 위에서 소개한
CHECK제약 조건이 위반될 때 발생하는 에러로, 예방 방법 적용 시 의도적으로 발생시켜division by zero를 사전에 차단하는 역할을 합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.