2026년 10월 14일 | DBMS Error 가이드
이 글에서 다루는 내용
22016 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22016 invalid argument for nth_value function 는?
PostgreSQL 에러 코드 22016은 nth_value() 윈도우 함수를 사용할 때 두 번째 인자로 유효하지 않은 값을 전달했을 때 발생하는 오류입니다. nth_value(value, n) 함수는 윈도우 프레임 내에서 N번째 행의 값을 반환하는 함수인데, 이 N 값이 1보다 작거나 NULL이거나 정수가 아닌 경우에 이 에러가 트리거됩니다. 실무에서는 동적으로 N 값을 계산하거나 사용자 입력값을 그대로 함수에 전달할 때 자주 발생하며, 잘못된 데이터 타입 캐스팅 과정에서도 종종 나타납니다.
주요 발생 원인
1. N 값으로 0 또는 음수를 전달하는 경우
nth_value() 함수의 두 번째 인자는 반드시 1 이상의 양의 정수여야 합니다. 개발자들이 배열 인덱스처럼 0부터 시작하는 값을 전달하거나, 계산 결과로 음수가 나오는 경우에 이 오류가 발생합니다. 예를 들어 “첫 번째 이전 행”을 얻으려고 nth_value(col, 0) 또는 nth_value(col, -1)을 호출하면 즉시 22016 에러가 발생합니다.
2. N 값으로 NULL을 전달하는 경우
서브쿼리나 외부 파라미터에서 계산된 값을 N으로 사용할 때, 해당 값이 NULL인 경우 PostgreSQL은 22016 에러를 발생시킵니다. 특히 LEFT JOIN 결과나 집계 함수가 결과를 반환하지 못하는 상황에서 NULL이 유입되는 경우가 많습니다. NULL 체크 없이 곧바로 nth_value() 함수에 전달하는 코드 패턴이 이 문제의 주요 원인입니다.
3. 잘못된 데이터 타입 또는 소수점 값을 전달하는 경우
nth_value()의 두 번째 인자는 정수(integer) 타입이어야 하지만, 실수(float)나 문자열(text) 형태의 값이 암묵적 또는 명시적 캐스팅 과정에서 잘못 처리되면 에러가 발생합니다. 예를 들어 평균 계산 결과(2.5 같은 소수)를 N으로 그대로 사용하거나, 사용자로부터 입력받은 문자열이 숫자로 제대로 변환되지 않은 경우에도 이 에러가 나타날 수 있습니다.
해결 방법
원인 1 해결: 0 또는 음수 방지 — GREATEST() 함수 활용
N 값이 항상 1 이상이 되도록 GREATEST() 함수를 사용하여 하한값을 보정합니다.
-- 잘못된 예시 (에러 발생)
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
-- 올바른 예시: GREATEST로 최소값 1 보장
SELECT
employee_id,
salary,
nth_value(salary, GREATEST(1, :input_n)) OVER (
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS nth_salary
FROM employees;
-- 동적 계산값을 사용하는 경우
SELECT
department_id,
employee_id,
salary,
nth_value(salary, GREATEST(1, rank_input)) OVER (
PARTITION BY department_id
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS target_salary
FROM (
SELECT
department_id,
employee_id,
salary,
COALESCE(some_calculated_rank, 1) AS rank_input
FROM employees
) sub;
원인 2 해결: NULL 값 방지 — COALESCE() + NULL 체크
N 값이 NULL일 가능성이 있는 경우 반드시 COALESCE()를 사용하거나 사전에 NULL 필터링을 수행합니다.
-- 잘못된 예시 (N이 NULL인 경우 에러 발생)
WITH params AS (
SELECT NULL::integer AS target_rank -- NULL 유입 상황
)
SELECT
e.employee_id,
e.salary,
nth_value(e.salary, p.target_rank) OVER (
ORDER BY e.salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS nth_salary
FROM employees e, params p;
-- ERROR: argument of nth_value must be greater than zero (NULL 포함)
-- 올바른 예시 1: COALESCE로 기본값 설정
WITH params AS (
SELECT NULL::integer AS target_rank
)
SELECT
e.employee_id,
e.salary,
nth_value(e.salary, COALESCE(p.target_rank, 1)) OVER (
ORDER BY e.salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS nth_salary
FROM employees e, params p;
-- 올바른 예시 2: NULL 행 자체를 필터링하는 방법
SELECT
employee_id,
salary,
nth_value(salary, ref_rank) OVER (
PARTITION BY department_id
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS nth_salary
FROM employees
WHERE ref_rank IS NOT NULL
AND ref_rank >= 1;
원인 3 해결: 올바른 타입 캐스팅 — FLOOR() + CAST() 활용
소수점 값이나 문자열 값을 정수로 안전하게 변환한 후 사용합니다.
-- 잘못된 예시 (소수점 값 사용)
SELECT
product_id,
price,
nth_value(price, 2.7) OVER ( -- 소수점 값은 에러 유발 가능
ORDER BY price DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS nth_price
FROM products;
-- 올바른 예시 1: FLOOR()로 정수 변환 후 GREATEST()로 하한 보장
SELECT
product_id,
price,
nth_value(price, GREATEST(1, FLOOR(2.7)::integer)) OVER (
ORDER BY price DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS nth_price
FROM products;
-- 올바른 예시 2: 사용자 입력 문자열을 안전하게 처리하는 함수 래핑
CREATE OR REPLACE FUNCTION safe_nth_value_rank(input_text TEXT)
RETURNS INTEGER AS $$
DECLARE
parsed_val INTEGER;
BEGIN
BEGIN
parsed_val := input_text::INTEGER;
EXCEPTION WHEN others THEN
parsed_val := 1; -- 파싱 실패 시 기본값
END;
IF parsed_val IS NULL OR parsed_val < 1 THEN
parsed_val := 1;
END IF;
RETURN parsed_val;
END;
$$ LANGUAGE plpgsql;
-- 사용 예시
SELECT
product_id,
price,
nth_value(price, safe_nth_value_rank('3')) OVER (
ORDER BY price DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS nth_price
FROM products;
종합 실무 예제: 안전한 nth_value 래퍼 뷰 생성
-- 실무에서 사용할 수 있는 안전한 패턴
CREATE OR REPLACE VIEW safe_salary_ranking AS
SELECT
department_id,
employee_id,
employee_name,
salary,
-- 항상 안전한 nth_value 사용 패턴
nth_value(salary, 1) OVER dept_window AS highest_salary,
nth_value(salary, 2) OVER dept_window AS second_highest_salary,
nth_value(salary, GREATEST(1, COALESCE(target_n, 3))) OVER dept_window AS custom_nth_salary,
ROW_NUMBER() OVER dept_window AS row_rank
FROM employees
WHERE target_n IS NULL OR target_n >= 1 -- NULL 또는 유효하지 않은 값 제외
WINDOW dept_window AS (
PARTITION BY department_id
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
);
예방 방법
1. 입력값 유효성 검사 함수 표준화
팀 내에서 nth_value()를 사용할 때는 항상 N 값을 래핑하는 표준 함수를 만들어 사용하도록 코딩 컨벤션을 수립하세요. GREATEST(1, COALESCE(n_value, 1)) 패턴을 공통 유틸리티로 만들어 두면, 개발자가 실수로 잘못된 값을 전달하는 것을 시스템 수준에서 방지할 수 있습니다. 또한 코드 리뷰 체크리스트에 nth_value() 사용 시 N 값 유효성 확인 항목을 추가하는 것을 권장합니다.
2. 테스트 케이스에 경계값 포함
nth_value()를 사용하는 쿼리에 대해 단위 테스트 또는 통합 테스트를 작성할 때, N=0, N=-1, N=NULL, N=소수점 같은 경계값 케이스를 반드시 포함하세요. CI/CD 파이프라인에서 이런 경계값 테스트가 자동으로 실행되도록 설정하면, 운영 환경에 배포되기 전에 잠재적인 22016 에러를 사전에 발견할 수 있습니다.
관련 에러
- 22012 (division_by_zero): 윈도우 함수 내부에서 나눗셈 연산 시 0으로 나누는 경우 발생하며, nth_value와 함께 복합 계산식에서 자주 동반 발생합니다.
- 22003 (numeric_value_out_of_range): N 값이 정수 범위를 초과하는 매우 큰 숫자일 때 발생할 수 있으며, 22016과 유사한 입력값 검증 문제입니다.
- 42883 (undefined_function):
nth_value()함수 자체가 잘못된 인자 타입으로 호출될 때 함수 오버로딩이 맞지 않아 발생하는 에러로, 22016과 혼동하기 쉽습니다. - 22001 (string_data_right_truncation): 윈도우 함수의 결과를 너무 작은 타입의 컬럼에 저장할 때 발생하며, nth_value 결과를 INSERT할 때 함께 발생하는 경우가 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.