2026년 06월 09일 | DBMS Error 가이드
이 글에서 다루는 내용
22013 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22013 invalid preceding or following size in window function 는?
PostgreSQL 에러 코드 22013은 윈도우 함수(Window Function)에서 ROWS BETWEEN 또는 RANGE BETWEEN 절에 지정한 PRECEDING(이전) 또는 FOLLOWING(이후) 크기 값이 유효하지 않을 때 발생합니다. 주로 해당 값이 음수이거나, 정수가 아닌 값이거나, NULL인 경우에 이 에러가 트리거됩니다. 윈도우 프레임(Window Frame)을 정의하는 과정에서 잘못된 오프셋 값을 사용하면 PostgreSQL은 쿼리를 실행하지 못하고 이 에러를 반환합니다.
주요 발생 원인
- PRECEDING/FOLLOWING 값에 음수(-) 값을 지정한 경우
윈도우 프레임 절에서 ROWS BETWEEN -1 PRECEDING AND CURRENT ROW처럼 음수 값을 오프셋으로 지정하면 PostgreSQL은 해당 프레임 정의가 논리적으로 불가능하다고 판단합니다. PRECEDING과 FOLLOWING의 오프셋은 반드시 0 이상의 양의 정수(또는 0)여야 합니다. 음수 오프셋은 SQL 표준에서도 허용하지 않는 문법이므로, 동적으로 값을 계산하여 윈도우 프레임에 전달할 때 특히 주의해야 합니다.
- 동적으로 계산된 오프셋 값이 NULL인 경우
애플리케이션 로직이나 서브쿼리에서 동적으로 계산된 값을 윈도우 프레임 오프셋으로 사용할 때, 해당 값이 NULL로 평가되면 이 에러가 발생합니다. 예를 들어 파라미터 바인딩이나 COALESCE 처리가 누락된 경우, NULL 오프셋이 그대로 전달되어 에러를 유발할 수 있습니다. 이는 특히 데이터 파이프라인이나 ETL 작업에서 자주 마주치는 패턴으로, 방어적인 NULL 처리가 필수적입니다.
- RANGE 모드에서 오프셋 데이터 타입 불일치 또는 비정수 값 사용
RANGE BETWEEN 절을 사용할 때 오프셋 값의 데이터 타입이 파티션 열(ORDER BY 컬럼)의 타입과 호환되지 않거나, ROWS BETWEEN 절에서 소수점 값(예: 1.5 PRECEDING)을 사용하는 경우 에러가 발생할 수 있습니다. ROWS 모드는 물리적인 행 수를 기준으로 하므로 정수만 허용하며, RANGE 모드는 논리적 범위를 사용하므로 ORDER BY 컬럼 타입과 일치하는 값 타입을 사용해야 합니다.
해결 방법
원인 1 해결: 음수 값 제거 및 양수 보정
음수 오프셋이 발생하는 상황에서는 GREATEST 함수를 활용하여 최솟값을 0으로 보장하거나, 로직 자체를 재검토해야 합니다.
-- 잘못된 예시: 음수 오프셋 사용 (에러 발생)
SELECT
employee_id,
salary,
AVG(salary) OVER (
ORDER BY hire_date
ROWS BETWEEN -1 PRECEDING AND CURRENT ROW -- 에러 발생!
) AS avg_salary
FROM employees;
-- 올바른 예시: 양수 오프셋 사용
SELECT
employee_id,
salary,
AVG(salary) OVER (
ORDER BY hire_date
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW -- 현재 행 포함 이전 1행
) AS avg_salary
FROM employees;
-- 동적 오프셋 계산 시 GREATEST로 음수 방지
DO $$
DECLARE
v_offset INTEGER := -1; -- 잘못된 입력값 시뮬레이션
BEGIN
-- 음수가 들어올 경우 0으로 보정
v_offset := GREATEST(v_offset, 0);
RAISE NOTICE 'Safe offset: %', v_offset;
END $$;
원인 2 해결: NULL 오프셋 방지를 위한 COALESCE 처리
-- 잘못된 예시: NULL 값이 오프셋으로 전달될 가능성 있음
WITH params AS (
SELECT NULL::INTEGER AS window_size -- NULL 값 시뮬레이션
)
SELECT
order_id,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN (SELECT window_size FROM params) PRECEDING AND CURRENT ROW
-- window_size가 NULL이면 에러 발생!
) AS running_total
FROM orders;
-- 올바른 예시: COALESCE로 NULL 방어 처리
WITH params AS (
SELECT COALESCE(NULL::INTEGER, 3) AS window_size -- NULL 시 기본값 3 사용
)
SELECT
order_id,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN (SELECT window_size FROM params) PRECEDING AND CURRENT ROW
) AS running_total
FROM orders;
-- 실무에서 자주 사용하는 패턴: 파라미터 안전 처리
CREATE OR REPLACE FUNCTION get_moving_average(
p_window_size INTEGER DEFAULT 7
)
RETURNS TABLE(order_date DATE, avg_amount NUMERIC) AS $$
BEGIN
-- 입력값 유효성 검사
IF p_window_size IS NULL OR p_window_size < 0 THEN
RAISE EXCEPTION '윈도우 크기는 0 이상의 정수여야 합니다. 입력값: %', p_window_size;
END IF;
RETURN QUERY
SELECT
o.order_date,
AVG(o.amount) OVER (
ORDER BY o.order_date
ROWS BETWEEN p_window_size PRECEDING AND CURRENT ROW
)
FROM orders o
ORDER BY o.order_date;
END;
$$ LANGUAGE plpgsql;
-- 함수 호출 예시
SELECT * FROM get_moving_average(7); -- 7일 이동 평균
SELECT * FROM get_moving_average(0); -- CURRENT ROW만
원인 3 해결: RANGE 모드에서 올바른 타입 사용
-- 잘못된 예시: ROWS 모드에서 소수점 오프셋 (에러 발생)
SELECT
sale_id,
amount,
SUM(amount) OVER (
ORDER BY sale_date
ROWS BETWEEN 1.5 PRECEDING AND CURRENT ROW -- 비정수 값으로 에러!
) AS running_sum
FROM sales;
-- 올바른 예시 1: ROWS 모드에서 정수 오프셋
SELECT
sale_id,
amount,
SUM(amount) OVER (
ORDER BY sale_date
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
) AS running_sum
FROM sales;
-- 올바른 예시 2: RANGE 모드에서 날짜 타입에 맞는 INTERVAL 사용
SELECT
sale_id,
sale_date,
amount,
SUM(amount) OVER (
ORDER BY sale_date
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW
) AS weekly_running_sum
FROM sales;
-- 올바른 예시 3: 숫자 컬럼에 RANGE 모드 사용
SELECT
product_id,
price,
AVG(price) OVER (
ORDER BY price
RANGE BETWEEN 100 PRECEDING AND 100 FOLLOWING
) AS avg_similar_price
FROM products;
예방 방법
- 입력값 유효성 검사 함수 또는 CHECK 제약을 활용한 방어적 프로그래밍
동적으로 윈도우 프레임 오프셋을 생성하는 모든 코드 경로에 유효성 검사 로직을 추가하세요. PostgreSQL의 CHECK 제약, DOMAIN 타입, 또는 별도의 유효성 검사 함수를 만들어 오프셋 값이 항상 0 이상의 정수임을 보장하는 것이 좋습니다. 특히 사용자 입력이나 외부 시스템에서 값을 받아오는 경우, 반드시 GREATEST(value, 0) 또는 COALESCE(value, default_value) 패턴을 적용하여 NULL 및 음수 값을 사전에 차단해야 합니다.
- ROWS vs RANGE 모드 선택 기준을 팀 내 코딩 표준으로 문서화
ROWS와 RANGE 모드의 차이를 명확히 이해하고, 팀 내 SQL 코딩 가이드에 각 모드의 사용 기준과 허용 데이터 타입을 문서화하세요. ROWS 모드는 반드시 정수 오프셋을 사용하고, RANGE 모드는 ORDER BY 컬럼의 데이터 타입에 호환되는 값(날짜 컬럼이면 INTERVAL, 숫자 컬럼이면 숫자 리터럴)을 사용해야 한다는 규칙을 코드 리뷰 체크리스트에 포함시키면 실수를 사전에 방지할 수 있습니다.
관련 에러
- 22000 (data_exception): 윈도우 함수 관련 일반적인 데이터 예외의 부모 에러 클래스입니다.
- 42P20 (windowing_error): 윈도우 함수 정의 자체가 잘못된 경우(예: 잘못된 프레임 구문) 발생하며, 22013과 함께 자주 마주치는 에러입니다.
- 22012 (division_by_zero): 윈도우 함수 내부에서 집계 연산 중 0으로 나누기가 발생할 때 나타나며, 데이터 품질 이슈로 인해 함께 발생하는 경우가 많습니다.
- 42883 (undefined_function): RANGE 모드에서 오프셋 타입이 ORDER BY 컬럼 타입과 호환되지 않는 연산자가 없을 때 발생할 수 있으며, 22013과 유사한 상황에서 혼동되기 쉽습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.