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

22014
2026년 10월 14일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22014 invalid argument for ntile function 는?

PostgreSQL 에러 코드 22014는 NTILE() 윈도우 함수에 유효하지 않은 인수를 전달했을 때 발생합니다. NTILE(n)은 결과 집합을 n개의 버킷(bucket)으로 균등하게 나누는 함수인데, 이때 인수 n이 NULL이거나 0 이하의 정수일 경우 이 에러가 트리거됩니다. 실무에서는 동적 쿼리나 사용자 입력값을 그대로 NTILE 함수에 넘길 때 자주 마주치게 되는 에러입니다.


주요 발생 원인

1. NTILE 인수로 0 또는 음수 값 전달

가장 흔한 원인입니다. NTILE(0) 또는 NTILE(-5) 처럼 1보다 작은 정수를 인수로 사용하면 PostgreSQL은 즉시 22014 에러를 발생시킵니다. 버킷 수가 수학적으로 의미가 없기 때문에 PostgreSQL이 이를 차단하는 것이며, 애플리케이션에서 동적으로 버킷 수를 계산하다가 의도치 않게 0이 되는 경우가 대표적입니다.

2. NTILE 인수로 NULL 값 전달

변수나 서브쿼리, 파라미터 바인딩을 통해 NTILE에 NULL 값이 전달되는 경우입니다. 예를 들어, 사용자 설정 테이블에서 버킷 수를 읽어 오는데 해당 컬럼 값이 NULL인 상황이 이에 해당합니다. NULL은 “알 수 없는 값”이므로 PostgreSQL은 이를 유효한 버킷 수로 처리할 수 없어 에러를 반환합니다.

3. 동적 SQL 또는 애플리케이션 레이어에서의 잘못된 값 주입

ORM이나 애플리케이션 코드에서 NTILE의 인수를 동적으로 생성할 때, 입력값 검증 없이 그대로 쿼리에 주입하는 경우입니다. 예를 들어, 사용자가 페이지당 항목 수를 입력하는 UI에서 비정상적인 값(0, 빈 문자열 등)이 넘어오면 해당 에러가 발생합니다. 이 경우 에러의 근본 원인이 SQL이 아닌 애플리케이션 레이어에 있어 추적이 어렵습니다.


해결 방법

원인 1 해결: 0 또는 음수 값 방지

NTILE 호출 전 GREATEST() 함수를 활용하거나 CASE 구문으로 값을 보정합니다.

-- 잘못된 예시: 0을 직접 전달
SELECT
    employee_id,
    salary,
    NTILE(0) OVER (ORDER BY salary DESC) AS bucket  -- 22014 에러 발생
FROM employees;

-- 올바른 예시 1: GREATEST로 최솟값 보장
SELECT
    employee_id,
    salary,
    NTILE(GREATEST(1, :bucket_count)) OVER (ORDER BY salary DESC) AS bucket
FROM employees;

-- 올바른 예시 2: CASE 구문으로 유효성 검사
SELECT
    employee_id,
    salary,
    NTILE(
        CASE
            WHEN :bucket_count <= 0 THEN 1
            ELSE :bucket_count
        END
    ) OVER (ORDER BY salary DESC) AS bucket
FROM employees;

원인 2 해결: NULL 값 처리

COALESCE() 함수를 사용하여 NULL을 기본값으로 대체합니다.

-- 잘못된 예시: 설정 테이블에서 NULL이 올 수 있는 상황
SELECT
    s.sale_id,
    s.amount,
    NTILE(cfg.bucket_count) OVER (ORDER BY s.amount DESC) AS tier
FROM sales s
CROSS JOIN config_table cfg;  -- cfg.bucket_count가 NULL이면 22014 에러

-- 올바른 예시: COALESCE로 NULL 방어 처리
SELECT
    s.sale_id,
    s.amount,
    NTILE(COALESCE(NULLIF(cfg.bucket_count, 0), 4)) OVER (ORDER BY s.amount DESC) AS tier
FROM sales s
CROSS JOIN config_table cfg;

-- 실무 예시: 사용자별 성과를 4분위로 분류
SELECT
    user_id,
    revenue,
    NTILE(COALESCE(NULLIF(p_bucket_count, 0), 4)) OVER (
        PARTITION BY department_id
        ORDER BY revenue DESC
    ) AS performance_quartile
FROM user_performance;

원인 3 해결: 동적 SQL에서의 안전한 값 전달

함수나 저장 프로시저 내에서 입력값을 검증한 뒤 NTILE을 호출합니다.

-- 안전한 함수 작성 예시
CREATE OR REPLACE FUNCTION get_employee_buckets(
    p_bucket_count INTEGER
)
RETURNS TABLE(
    employee_id INTEGER,
    salary NUMERIC,
    bucket INTEGER
) AS $$
DECLARE
    v_safe_bucket INTEGER;
BEGIN
    -- 입력값 검증 및 보정
    IF p_bucket_count IS NULL OR p_bucket_count <= 0 THEN
        RAISE WARNING 'bucket_count이 유효하지 않습니다 (%). 기본값 4로 대체합니다.', p_bucket_count;
        v_safe_bucket := 4;
    ELSE
        v_safe_bucket := p_bucket_count;
    END IF;

    RETURN QUERY
    SELECT
        e.employee_id,
        e.salary,
        NTILE(v_safe_bucket) OVER (ORDER BY e.salary DESC)::INTEGER AS bucket
    FROM employees e;
END;
$$ LANGUAGE plpgsql;

-- 함수 호출
SELECT * FROM get_employee_buckets(0);    -- 경고 후 기본값 4 사용
SELECT * FROM get_employee_buckets(NULL); -- 경고 후 기본값 4 사용
SELECT * FROM get_employee_buckets(5);    -- 정상 처리

-- CHECK 제약조건으로 테이블 레벨에서 방어
ALTER TABLE config_table
    ADD CONSTRAINT chk_bucket_count_positive
    CHECK (bucket_count >= 1);

예방 방법

1. 입력값 검증 레이어를 애플리케이션과 DB 양쪽에 모두 구현하세요

NTILE에 전달되는 모든 동적 값은 애플리케이션 레이어에서 1차 검증을 거치고, DB 레이어에서도 GREATEST(1, value) 또는 COALESCE(NULLIF(value, 0), default) 패턴으로 2차 방어를 적용하는 이중 검증 구조를 갖추는 것이 중요합니다. 이렇게 하면 어느 한쪽에서 실수가 있어도 에러가 전파되지 않습니다.

2. 통합 테스트에 경계값 케이스를 반드시 포함하세요

NTILE을 사용하는 쿼리나 함수에 대해 0, -1, NULL, 그리고 매우 큰 수(예: 전체 행 수보다 큰 값)를 인수로 전달하는 테스트 케이스를 작성해 두어야 합니다. CI/CD 파이프라인에 이 테스트를 포함시키면 코드 변경 시 회귀를 조기에 발견할 수 있습니다.


관련 에러

  • 22003 (numeric_value_out_of_range): NTILE의 인수가 integer 범위를 초과하는 매우 큰 값일 때 발생할 수 있습니다.
  • 22012 (division_by_zero): 윈도우 함수 내부 연산에서 0으로 나누기 시도 시 발생하며, 잘못된 집계 결과가 NTILE과 함께 사용될 때 연쇄적으로 나타나기도 합니다.
  • 42883 (undefined_function): NTILE에 정수가 아닌 타입(예: FLOAT, TEXT)을 전달할 경우 함수 오버로딩을 찾지 못해 발생하는 에러로, 22014와 혼동하기 쉽습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기