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

22016
2026년 08월 10일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22016 invalid argument for nth_value function 는?

PostgreSQL 에러 코드 22016은 nth_value() 윈도우 함수에 잘못된 인수가 전달될 때 발생하는 오류입니다. nth_value(value, n) 함수는 윈도우 프레임 내에서 N번째 행의 값을 반환하는데, 이때 두 번째 인수인 n이 반드시 1 이상의 양의 정수여야 한다는 제약이 있습니다. n에 0이나 음수, 또는 NULL이 전달되는 경우 PostgreSQL은 즉시 이 에러를 발생시키며 쿼리 실행을 중단합니다.

주요 발생 원인

  • n 값으로 0 또는 음수를 전달하는 경우

nth_value() 함수의 두 번째 인수는 반드시 1 이상의 양의 정수여야 합니다. 개발 과정에서 동적으로 생성된 값이나 사용자 입력값이 그대로 전달될 때, 해당 값이 0이거나 음수이면 즉시 에러가 발생합니다. 특히 ROW_NUMBER 기반 계산이나 배열 인덱스를 직접 재사용하는 코드에서 off-by-one 실수로 인해 0이 전달되는 경우가 매우 흔합니다.

“`sql

— 에러 발생 예시: n에 0을 전달

SELECT

employee_id,

salary,

nth_value(salary, 0) OVER (ORDER BY salary DESC) AS nth_salary

FROM employees;

— ERROR: argument of nth_value must be greater than zero

— 에러 발생 예시: n에 음수를 전달

SELECT

employee_id,

salary,

nth_value(salary, -1) OVER (ORDER BY salary DESC) AS nth_salary

FROM employees;

— ERROR: argument of nth_value must be greater than zero

“`

  • 동적 쿼리 또는 파라미터 바인딩에서 NULL 또는 잘못된 값 전달

애플리케이션 레이어에서 동적으로 쿼리를 생성하거나, 사용자 입력을 바인딩 파라미터로 전달할 때 값 검증 없이 nth_value()의 두 번째 인수로 넘기는 경우 에러가 발생합니다. 특히 nth_value(column, $1::int) 형태로 파라미터를 전달할 때 $1이 NULL이면 PostgreSQL은 22016 에러를 반환합니다. 이는 백엔드 API에서 입력 유효성 검사를 생략했을 때 자주 발생하는 패턴입니다.

“`sql

— 에러 발생 예시: NULL이 전달되는 경우

DO $$

DECLARE

v_n INTEGER := NULL;

BEGIN

— 아래 쿼리는 22016 에러 발생

EXECUTE format(

‘SELECT nth_value(salary, %s) OVER (ORDER BY salary DESC) FROM employees’,

v_n

);

END;

$$;

— 실무에서 파라미터 바인딩 시 위험한 패턴

— Python 예시: cursor.execute(“SELECT nth_value(col, %s) OVER (…)”, (user_input,))

— user_input이 None이거나 0이면 에러 발생

“`

  • 서브쿼리 또는 계산식으로 도출된 n 값이 유효하지 않은 경우

nth_value()의 두 번째 인수에 상수가 아닌 서브쿼리나 계산식을 직접 사용할 수 없으며, 미리 계산된 값을 사용해야 합니다. 데이터 집계 결과나 COUNT() 값을 기반으로 N번째를 구하려는 시도에서, 데이터가 없거나 집계 결과가 0인 경우 의도치 않게 0이 전달되어 에러가 발생하는 경우가 있습니다. 이런 패턴은 보고서 쿼리나 대시보드용 집계 쿼리에서 특히 자주 발견됩니다.

“`sql

— 위험한 패턴: 집계 결과를 그대로 사용

— 아래처럼 직접 서브쿼리를 nth_value 인수로 사용 불가

SELECT

department_id,

salary,

— 이 방식은 문법 오류 또는 런타임 오류 가능

nth_value(salary, (SELECT COUNT(*)/2 FROM employees))

OVER (PARTITION BY department_id ORDER BY salary DESC)

FROM employees;

“`

해결 방법

원인 1 해결: GREATEST() 함수로 n 값의 최솟값을 보장

가장 간단한 방법은 GREATEST() 함수를 사용하여 n 값이 반드시 1 이상이 되도록 강제하는 것입니다.

-- 해결책: GREATEST()로 최솟값 1 보장
SELECT
    employee_id,
    department_id,
    salary,
    nth_value(salary, GREATEST(1, :user_n_value)) 
        OVER (
            PARTITION BY department_id 
            ORDER BY salary DESC
            ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
        ) AS nth_salary
FROM employees;

원인 2 해결: NULL 및 범위 검사 후 조건 분기 처리

동적 값이나 파라미터를 사용할 때는 반드시 사전 검증 로직을 추가해야 합니다.

-- 해결책: CASE WHEN으로 NULL 및 유효하지 않은 값 처리
DO $$
DECLARE
    v_n INTEGER := NULL; -- 외부에서 전달된 값
    v_safe_n INTEGER;
BEGIN
    -- 안전한 값으로 정규화
    v_safe_n := CASE 
        WHEN v_n IS NULL OR v_n < 1 THEN 1
        ELSE v_n
    END;

    RAISE NOTICE 'Using n = %', v_safe_n;

    -- 이제 안전하게 사용 가능
    -- nth_value(salary, v_safe_n) OVER (...)
END;
$$;

-- 실제 쿼리에서의 방어적 처리
SELECT
    employee_id,
    salary,
    CASE 
        WHEN :input_n IS NULL OR :input_n < 1 
        THEN NULL  -- 유효하지 않은 경우 NULL 반환
        ELSE nth_value(salary, :input_n) 
             OVER (ORDER BY salary DESC 
                   ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
    END AS safe_nth_salary
FROM employees;

원인 3 해결: CTE를 활용하여 n 값을 사전 계산 후 검증

서브쿼리 기반의 동적 N 값은 CTE에서 먼저 계산하고 검증하는 것이 좋습니다.

-- 해결책: CTE로 n 값 사전 계산 및 유효성 검사
WITH config AS (
    SELECT 
        GREATEST(1, COUNT(*) / 2) AS target_n  -- 0 방지
    FROM employees
    WHERE department_id = 10
),
ranked_employees AS (
    SELECT
        e.employee_id,
        e.department_id,
        e.salary,
        c.target_n,
        ROW_NUMBER() OVER (ORDER BY e.salary DESC) AS rn
    FROM employees e
    CROSS JOIN config c
)
SELECT
    employee_id,
    department_id,
    salary,
    -- target_n은 이미 1 이상임이 보장됨
    nth_value(salary, target_n) 
        OVER (
            ORDER BY salary DESC
            ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
        ) AS nth_salary
FROM ranked_employees;

-- 더 안전한 방법: FIRST_VALUE / LAST_VALUE 조합으로 대체
-- N번째 값이 특수한 경우(1번째 또는 마지막)라면 아래 함수로 대체 가능
SELECT
    employee_id,
    salary,
    FIRST_VALUE(salary) OVER (ORDER BY salary DESC) AS highest_salary,
    LAST_VALUE(salary)  OVER (
        ORDER BY salary DESC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS lowest_salary
FROM employees;

예방 방법

  • 입력값 유효성 검사 레이어 추가 (애플리케이션 & DB 레벨)

nth_value()에 전달되는 N 값은 반드시 애플리케이션 레이어와 DB 레이어 모두에서 이중으로 검증해야 합니다. DB 레이어에서는 래퍼 함수(Wrapper Function)를 만들어 항상 안전한 값만 nth_value()로 전달되도록 강제하는 것이 가장 효과적인 방어책입니다.

“`sql

— 안전한 래퍼 함수 생성

CREATE OR REPLACE FUNCTION safe_nth_value(

p_value ANYELEMENT,

p_n INTEGER

) RETURNS ANYELEMENT AS $$

BEGIN

IF p_n IS NULL OR p_n < 1 THEN

RAISE EXCEPTION ‘nth_value argument must be >= 1, got: %’, p_n

USING ERRCODE = ‘22016’;

END IF;

RETURN p_value; — 실제로는 윈도우 함수라 직접 래핑 불가, 검증용 예시

END;

$$ LANGUAGE plpgsql IMMUTABLE;

— 쿼리 작성 시 항상 GREATEST(1, n) 패턴을 팀 코딩 컨벤션으로 채택

— BAD: nth_value(col, n)

— GOOD: nth_value(col, GREATEST(1, COALESCE(n, 1)))

“`

  • 통합 테스트에 경계값 케이스 추가

CI/CD 파이프라인의 DB 통합 테스트에 N=0, N=-1, N=NULL 등의 경계값 케이스를 반드시 포함해야 합니다. 특히 동적 쿼리를 생성하는 리포팅 모듈이나 대시보드 쿼리는 실제 데이터가 없는 빈 테이블 케이스에서도 동작을 검증해야 합니다. pgTAP과 같은 PostgreSQL 테스트 프레임워크를 활용하면 이러한 에러 케이스를 체계적으로 관리할 수 있습니다.

“`sql

— pgTAP을 활용한 경계값 테스트 예시

SELECT throws_ok(

$$ SELECT nth_value(1, 0) OVER () $$,

‘22016’,

‘argument of nth_value must be greater than zero’,

‘nth_value with n=0 should throw 22016’

);

SELECT throws_ok(

$$ SELECT nth_value(1, -5) OVER () $$,

‘22016’,

‘argument of nth_value must be greater than zero’,

‘nth_value with negative n should throw 22016’

);

“`

관련 에러

  • 22012 (division_by_zero): 0으로 나누기 오류로, N 값을 계산하는 과정에서 나눗셈 연산이 포함된 경우 함께 발생할 수 있습니다.
  • 22003 (numeric_value_out_of_range): N 값이 INTEGER 범위를 초과하는 매우 큰 숫자가 전달될 때 발생할 수 있습니다.
  • 42883 (undefined_function): nth_value() 함수 자체를 잘못된 타입 인수로 호출했을 때 함수를 찾지 못하는 에러로 이어질 수 있습니다.
  • 42P20 (windowing_error): nth_value()를 포함한 윈도우 함수의 OVER 절이 잘못 구성되었을 때 발생하는 관련 에러입니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기