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

2201W
2026년 08월 13일 | DBMS Error 가이드

이 글에서 다루는 내용

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

2201W invalid row count in limit clause 는?

PostgreSQL 에러 코드 2201WLIMIT 절에 유효하지 않은 행 수(row count) 값이 지정되었을 때 발생하는 에러입니다. 구체적으로는 LIMIT 절에 음수(negative number)나 허용되지 않는 형태의 값이 입력될 경우 PostgreSQL이 쿼리 실행을 거부하면서 이 에러를 반환합니다. 이 에러는 SQL 표준(SQLSTATE 22003 계열)에서 파생된 데이터 예외(Data Exception) 범주에 속하며, 주로 동적 쿼리 생성 또는 애플리케이션 레이어에서 LIMIT 값을 외부 입력으로 받아 처리할 때 빈번히 발생합니다.


주요 발생 원인

1. LIMIT 절에 음수 값 사용

가장 흔한 원인은 LIMIT 절에 음수 값을 직접 또는 변수로 전달하는 경우입니다. 예를 들어, 애플리케이션에서 페이지 크기(page size)를 계산하다가 잘못된 로직으로 인해 음수가 생성되고, 이 값이 그대로 SQL 쿼리에 바인딩될 때 발생합니다. PostgreSQL은 LIMIT -1과 같은 표현을 허용하지 않으며(일부 DB와 달리), 반드시 0 이상의 정수여야 합니다.

2. 동적 쿼리 생성 시 잘못된 변수 바인딩

ORM(Object-Relational Mapping) 도구나 동적 SQL을 생성하는 코드에서 LIMIT 값을 외부 파라미터로부터 받아 검증 없이 사용하는 경우입니다. 사용자 입력값, 설정 파일 값, 또는 다른 쿼리 결과로부터 계산된 값이 음수이거나 NULL인 경우 이 에러가 발생할 수 있습니다. 특히 페이지네이션(pagination) 로직에서 (page - 1) * page_size 계산 시 page가 0이나 음수이면 문제가 생깁니다.

3. PL/pgSQL 함수 또는 프로시저 내부의 동적 쿼리

PL/pgSQL로 작성된 함수나 저장 프로시저 내에서 EXECUTE 구문을 통해 동적 SQL을 실행할 때, LIMIT에 해당하는 변수가 적절히 검증되지 않으면 이 에러가 발생합니다. 함수 인자로 전달된 limit 값이 호출 시점에 음수이거나, 내부 계산 결과가 음수가 되는 경우가 여기에 해당합니다. 이 경우 에러 메시지만으로는 원인을 파악하기 어렵기 때문에 디버깅이 복잡해질 수 있습니다.


해결 방법

원인 1 해결: 음수 값 방지 및 GREATEST 함수 활용

LIMIT 값이 반드시 0 이상이 되도록 GREATEST 함수를 사용하여 안전하게 처리합니다.

-- 잘못된 예 (에러 발생)
SELECT * FROM orders LIMIT -10;
-- ERROR: ERROR:  invalid row count in limit clause

-- 올바른 예 1: GREATEST로 음수 방지
SELECT * FROM orders
LIMIT GREATEST(0, -10);  -- 결과: LIMIT 0 (빈 결과 반환)

-- 올바른 예 2: CASE 문으로 유효성 검사
SELECT * FROM orders
LIMIT CASE WHEN 10 > 0 THEN 10 ELSE 10 END;

-- 실무 예: 페이지네이션 적용 시
-- 변수 값이 음수일 수 있다고 가정
DO $$
DECLARE
    v_limit INT := -5;  -- 잘못된 입력 시뮬레이션
BEGIN
    -- 안전한 방어 처리
    IF v_limit < 0 THEN
        v_limit := 0;
    END IF;
    RAISE NOTICE 'Safe limit: %', v_limit;
END $$;

원인 2 해결: 동적 쿼리에서 입력값 검증

애플리케이션 레이어 또는 SQL 레이어에서 LIMIT 값을 반드시 검증합니다.

-- 잘못된 동적 쿼리 패턴
-- (애플리케이션에서 page=0으로 입력된 경우)
-- LIMIT (0 - 1) * 10 => LIMIT -10 -> 에러 발생

-- 안전한 PL/pgSQL 함수 작성 예
CREATE OR REPLACE FUNCTION get_paginated_orders(
    p_page      INT DEFAULT 1,
    p_page_size INT DEFAULT 10
)
RETURNS SETOF orders AS $$
DECLARE
    v_limit  INT;
    v_offset INT;
BEGIN
    -- 입력값 유효성 검사
    IF p_page < 1 THEN
        RAISE EXCEPTION 'page must be >= 1, got: %', p_page;
    END IF;
    IF p_page_size < 1 THEN
        RAISE EXCEPTION 'page_size must be >= 1, got: %', p_page_size;
    END IF;

    v_limit  := p_page_size;
    v_offset := (p_page - 1) * p_page_size;

    RETURN QUERY
    SELECT *
    FROM orders
    ORDER BY created_at DESC
    LIMIT v_limit
    OFFSET v_offset;
END;
$$ LANGUAGE plpgsql;

-- 안전한 호출
SELECT * FROM get_paginated_orders(1, 20);

-- 잘못된 입력 테스트
SELECT * FROM get_paginated_orders(0, 20);
-- ERROR: page must be >= 1, got: 0

원인 3 해결: 동적 SQL 실행 시 안전 처리

EXECUTE 구문 사용 시 LIMIT 값을 반드시 검증 후 사용합니다.

-- 위험한 동적 SQL 패턴
CREATE OR REPLACE FUNCTION unsafe_query(p_limit INT)
RETURNS void AS $$
BEGIN
    EXECUTE 'SELECT * FROM orders LIMIT ' || p_limit;
    -- p_limit이 음수면 2201W 에러 발생!
END;
$$ LANGUAGE plpgsql;

-- 안전한 동적 SQL 패턴 (USING 절 활용 + 검증)
CREATE OR REPLACE FUNCTION safe_dynamic_query(p_limit INT)
RETURNS SETOF orders AS $$
DECLARE
    v_safe_limit INT;
BEGIN
    -- 방어 로직
    v_safe_limit := GREATEST(0, COALESCE(p_limit, 0));

    RETURN QUERY EXECUTE
        'SELECT * FROM orders ORDER BY id LIMIT $1'
    USING v_safe_limit;
END;
$$ LANGUAGE plpgsql;

-- 테스트
SELECT * FROM safe_dynamic_query(-5);  -- 빈 결과 반환 (에러 없음)
SELECT * FROM safe_dynamic_query(10);  -- 정상 10건 반환
SELECT * FROM safe_dynamic_query(NULL); -- 0건 반환 (에러 없음)

예방 방법

1. 데이터베이스 레벨의 입력값 검증 래퍼 함수 구축

애플리케이션에서 LIMIT 값을 신뢰하지 말고, 데이터베이스 레벨에서 LIMIT/OFFSET 값을 항상 검증하는 공통 유틸리티 함수를 만들어 사용하세요. GREATEST(1, COALESCE(p_limit, 10))와 같은 패턴을 표준화하면 여러 쿼리에서 일관된 안전성을 확보할 수 있습니다. 또한, CHECK 제약이나 도메인(DOMAIN) 타입을 활용하여 양수 정수만 허용하는 커스텀 타입을 정의하는 것도 효과적입니다.

-- 재사용 가능한 안전 LIMIT 함수
CREATE OR REPLACE FUNCTION safe_limit(p_limit INT, p_default INT DEFAULT 10)
RETURNS INT AS $$
BEGIN
    RETURN GREATEST(1, COALESCE(p_limit, p_default));
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 사용 예
SELECT * FROM orders LIMIT safe_limit(-5);    -- LIMIT 1
SELECT * FROM orders LIMIT safe_limit(NULL);   -- LIMIT 10
SELECT * FROM orders LIMIT safe_limit(50);     -- LIMIT 50

2. 통합 테스트 및 모니터링에 경계값 테스트 포함

CI/CD 파이프라인의 통합 테스트에 LIMIT 값에 대한 경계값 테스트(boundary testing)를 반드시 포함하세요. p_limit = 0, p_limit = -1, p_limit = NULL 등의 케이스를 테스트 스위트에 추가하여 배포 전에 이 에러를 사전에 차단할 수 있습니다. 아울러 PostgreSQL 로그에서 SQLSTATE 2201W 패턴을 모니터링하여 운영 중 발생 즉시 알림을 받도록 설정하는 것을 권장합니다.


관련 에러

  • 22003 (numeric_value_out_of_range): 숫자 값이 허용 범위를 초과할 때 발생하며, LIMIT 관련 에러와 같은 데이터 예외 카테고리(Class 22)에 속합니다.
  • 2201X (invalid_row_count_in_result_offset_clause): OFFSET 절에 유효하지 않은 값(음수 등)이 지정될 때 발생하는 에러로, 2201W와 쌍으로 발생하는 경우가 많습니다. LIMIT과 OFFSET을 함께 사용하는 페이지네이션 쿼리에서 두 에러 모두 방어해야 합니다.
  • 42601 (syntax_error): LIMIT 절에 완전히 잘못된 형식의 값이 들어올 때는 2201W 대신 문법 에러가 먼저 발생할 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기