2026년 08월 13일 | DBMS Error 가이드
이 글에서 다루는 내용
22013 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22013 invalid preceding or following size in window function 는?
PostgreSQL 에러 코드 22013은 윈도우 함수(Window Function)에서 ROWS, RANGE, 또는 GROUPS 프레임을 정의할 때 PRECEDING 또는 FOLLOWING 값으로 유효하지 않은 크기를 지정했을 때 발생합니다. 예를 들어, 음수 값이나 NULL 값을 프레임 경계값으로 사용하려 할 때 이 에러가 나타납니다. 이 에러는 주로 동적으로 생성된 쿼리나 사용자 입력값을 윈도우 함수에 그대로 전달할 때 실무 환경에서 자주 발생합니다.
주요 발생 원인
1. 음수(Negative) 값을 PRECEDING/FOLLOWING 오프셋으로 사용
윈도우 함수의 프레임 경계(PRECEDING, FOLLOWING)에는 반드시 0 이상의 정수 또는 유효한 interval 값이 지정되어야 합니다. 실수로 음수 값이 계산되거나 파라미터로 전달되면 PostgreSQL은 즉시 22013 에러를 발생시킵니다.
-- 에러 발생 예시: 음수 PRECEDING 사용
SELECT
employee_id,
salary,
SUM(salary) OVER (
ORDER BY employee_id
ROWS BETWEEN -1 PRECEDING AND CURRENT ROW -- 음수 오프셋 → 에러!
) AS running_total
FROM employees;
-- ERROR: invalid preceding or following size in window function
2. NULL 값을 프레임 오프셋으로 전달
동적 쿼리나 변수를 통해 프레임 크기를 지정할 때, 해당 변수가 NULL인 경우에도 동일한 에러가 발생합니다. 특히 PL/pgSQL 함수나 애플리케이션 레이어에서 파라미터 바인딩을 통해 윈도우 크기를 동적으로 전달할 때 NULL 체크를 빠뜨리는 경우가 많습니다.
-- 에러 발생 예시: NULL을 오프셋으로 사용
DO $$
DECLARE
v_window_size INT := NULL; -- NULL 값
BEGIN
-- NULL 값을 그대로 사용하면 에러 발생
EXECUTE format(
'SELECT SUM(salary) OVER (ORDER BY hire_date ROWS BETWEEN %s PRECEDING AND CURRENT ROW) FROM employees',
v_window_size
);
END;
$$;
-- ERROR: invalid preceding or following size in window function
3. RANGE 프레임에서 호환되지 않는 데이터 타입 또는 잘못된 interval 값 사용
RANGE 모드에서는 오프셋 값이 ORDER BY 컬럼의 데이터 타입과 반드시 호환되어야 합니다. 날짜 컬럼에 정수 오프셋을 지정하거나, interval 값이 음수로 계산되는 경우에도 이 에러가 발생할 수 있습니다. 실무에서는 복잡한 비즈니스 로직에서 interval 값이 동적으로 계산될 때 특히 주의가 필요합니다.
-- 에러 발생 예시: RANGE에서 음수 interval 사용
SELECT
order_date,
revenue,
SUM(revenue) OVER (
ORDER BY order_date
RANGE BETWEEN INTERVAL '-7 days' PRECEDING AND CURRENT ROW -- 음수 interval → 에러!
) AS weekly_revenue
FROM sales;
-- ERROR: invalid preceding or following size in window function
해결 방법
원인 1 해결: 음수 값 방지 — GREATEST() 함수 활용
동적으로 계산된 오프셋 값이 음수가 될 가능성이 있다면, GREATEST() 함수를 사용해 최솟값을 0으로 보정합니다.
-- 해결: GREATEST()로 최솟값 0 보장
DO $$
DECLARE
v_offset INT := -1; -- 음수가 될 수 있는 값
v_safe_offset INT;
BEGIN
v_safe_offset := GREATEST(v_offset, 0); -- 0 이상으로 보정
RAISE NOTICE 'Safe offset: %', v_safe_offset;
END;
$$;
-- 실제 쿼리에서 안전하게 사용
SELECT
employee_id,
salary,
SUM(salary) OVER (
ORDER BY employee_id
ROWS BETWEEN GREATEST(0, 3 - 5) PRECEDING AND CURRENT ROW
-- GREATEST(0, -2) = 0 → 안전하게 처리됨
) AS safe_running_total
FROM employees;
원인 2 해결: NULL 체크 후 기본값 적용
PL/pgSQL이나 동적 쿼리에서 파라미터를 사용할 경우, COALESCE() 함수나 명시적 NULL 체크를 통해 안전한 기본값을 설정합니다.
-- 해결: COALESCE로 NULL을 기본값으로 대체
CREATE OR REPLACE FUNCTION get_moving_average(
p_window_size INT DEFAULT 7
)
RETURNS TABLE(sale_date DATE, avg_revenue NUMERIC) AS $$
DECLARE
v_safe_window INT;
BEGIN
-- NULL 또는 음수 방지
v_safe_window := GREATEST(COALESCE(p_window_size, 7), 1);
RETURN QUERY
SELECT
s.sale_date,
AVG(s.revenue) OVER (
ORDER BY s.sale_date
ROWS BETWEEN (v_safe_window - 1) PRECEDING AND CURRENT ROW
)
FROM sales s
ORDER BY s.sale_date;
END;
$$ LANGUAGE plpgsql;
-- 안전한 호출
SELECT * FROM get_moving_average(NULL); -- NULL → 기본값 7 사용
SELECT * FROM get_moving_average(-3); -- 음수 → GREATEST로 1 사용
SELECT * FROM get_moving_average(30); -- 정상값 사용
원인 3 해결: RANGE 모드에서 타입 일치 및 양수 interval 보장
-- 잘못된 코드 (음수 interval)
-- RANGE BETWEEN INTERVAL '-7 days' PRECEDING AND CURRENT ROW
-- 해결: 양수 interval 사용 + 방향은 PRECEDING/FOLLOWING으로 표현
SELECT
order_date,
revenue,
SUM(revenue) OVER (
ORDER BY order_date
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW -- 양수로 수정
) AS weekly_revenue
FROM sales
ORDER BY order_date;
-- 동적 interval을 사용하는 경우 ABS() 활용
DO $$
DECLARE
v_days INT := -7; -- 음수가 될 수 있는 값
v_interval INTERVAL;
BEGIN
v_interval := make_interval(days => ABS(v_days)); -- ABS()로 절대값 사용
RAISE NOTICE 'Safe interval: %', v_interval;
END;
$$;
예방 방법
1. 입력값 유효성 검증 레이어를 함수 내부에 반드시 포함하기
윈도우 함수 오프셋을 동적으로 받는 모든 함수나 프로시저에는 COALESCE(), GREATEST(), ABS() 등의 방어 코드를 표준 패턴으로 적용해야 합니다. 코드 리뷰 체크리스트에 “윈도우 함수 오프셋 유효성 검증” 항목을 추가하여 팀 전체가 이 패턴을 공유하는 것이 중요합니다.
-- 권장 표준 패턴: 윈도우 오프셋 안전 처리 함수
CREATE OR REPLACE FUNCTION safe_window_offset(p_offset ANYELEMENT)
RETURNS INT AS $$
BEGIN
RETURN GREATEST(COALESCE(p_offset::INT, 0), 0);
END;
$$ LANGUAGE plpgsql IMMUTABLE;
2. 통합 테스트에 경계값(Boundary Value) 케이스 반드시 포함하기
단위 테스트 및 통합 테스트에서 윈도우 함수 오프셋에 대해 0, -1, NULL, 최대 정수값 등의 경계값 테스트 케이스를 반드시 포함해야 합니다. CI/CD 파이프라인에서 이러한 경계값 테스트가 자동으로 실행되도록 구성하면 프로덕션 환경에서 에러가 노출되는 것을 사전에 방지할 수 있습니다.
관련 에러
- 22012 (division_by_zero): 윈도우 함수 내부 계산에서 0으로 나누기가 발생할 때 나타나며, 동적 오프셋 계산 로직에서 함께 발생하는 경우가 있습니다.
- 42P20 (windowing_error): 윈도우 함수 정의 자체가 문법적으로 잘못된 경우(예: ROWS와 RANGE 혼용, ORDER BY 누락 시 RANGE 사용 등)에 발생하는 에러로, 22013과 혼동되기 쉽습니다.
- 22003 (numeric_value_out_of_range): 오프셋 값이 지나치게 큰 경우 또는 내부 계산 중 오버플로우가 발생할 때 함께 나타날 수 있는 관련 에러입니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.