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

02001
2026년 08월 02일 | DBMS Error 가이드

이 글에서 다루는 내용

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

02001 no additional dynamic result sets returned 는?

PostgreSQL 에러 코드 02001은 SQLSTATE 클래스 02(No Data)에 속하는 에러로, 저장 프로시저(Stored Procedure)가 반환하기로 선언된 동적 결과 집합(Dynamic Result Set)이 실제로는 더 이상 존재하지 않을 때 발생합니다. 쉽게 말해, 호출자(caller)가 프로시저로부터 추가적인 결과 집합을 기대하고 있지만, 프로시저 내부에서 더 이상 반환할 커서나 결과가 없는 상태를 의미합니다. 이 에러는 주로 SQL/PSM(Persistent Stored Modules) 표준을 따르는 환경이나 JDBC, ODBC 드라이버를 통해 복수의 결과 집합을 처리하는 애플리케이션에서 자주 목격됩니다.


주요 발생 원인

1. 프로시저가 선언한 결과 집합 수보다 적게 반환하는 경우

저장 프로시저를 정의할 때 DYNAMIC RESULT SETS n 구문으로 반환할 결과 집합의 수를 명시했지만, 실제 실행 시 조건 분기나 예외 처리로 인해 선언된 수보다 적은 결과 집합이 반환되는 경우 이 에러가 발생합니다. 예를 들어, 2개의 결과 집합을 반환하겠다고 선언했으나 특정 조건에서 1개만 반환되면 호출 측에서 두 번째 결과를 읽으려 할 때 02001이 트리거됩니다.

2. 커서(Cursor)를 닫은 후 추가 Fetch를 시도하는 경우

프로시저 내부에서 커서를 열고(OPEN) 사용한 뒤 명시적으로 닫았는데(CLOSE), 외부 애플리케이션이나 드라이버가 해당 커서에서 추가 데이터를 가져오려(Fetch) 시도하면 동적 결과 집합이 더 이상 유효하지 않다는 신호로 02001이 발생할 수 있습니다. 특히 JDBC의 getMoreResults() 같은 API를 잘못 사용할 때 흔하게 나타납니다.

3. 애플리케이션 드라이버의 결과 집합 순회 로직 오류

JDBC나 ODBC 드라이버가 저장 프로시저의 결과를 순회할 때, 실제 반환된 결과 집합의 수를 정확히 파악하지 못하고 루프를 한 번 더 돌거나, getMoreResults()를 과도하게 호출하면 PostgreSQL 서버 측에서 02001을 반환합니다. 드라이버 버전 불일치나 프로시저 메타데이터를 잘못 파싱하는 버그가 원인이 되기도 합니다.


해결 방법

원인 1 해결: 반환 결과 집합 수 일치시키기

프로시저에서 실제 반환하는 결과 집합 수와 선언부를 일치시키는 것이 가장 근본적인 해결책입니다.

-- 문제가 있는 프로시저 예시 (2개를 선언했으나 조건에 따라 1개만 반환)
CREATE OR REPLACE PROCEDURE get_employee_data(p_dept_id INT)
LANGUAGE plpgsql
AS $$
DECLARE
    cur1 REFCURSOR := 'cur_employees';
    cur2 REFCURSOR := 'cur_salaries';
BEGIN
    -- 첫 번째 결과 집합: 직원 목록
    OPEN cur1 FOR
        SELECT emp_id, emp_name, dept_id
        FROM employees
        WHERE dept_id = p_dept_id;

    -- 조건에 따라 두 번째 결과 집합이 열리지 않을 수 있음 (문제!)
    IF p_dept_id > 0 THEN
        OPEN cur2 FOR
            SELECT emp_id, salary, bonus
            FROM salaries
            WHERE dept_id = p_dept_id;
    END IF;
END;
$$;

-- 수정된 프로시저: 항상 두 번째 커서도 반환 (빈 결과라도)
CREATE OR REPLACE PROCEDURE get_employee_data_fixed(p_dept_id INT)
LANGUAGE plpgsql
AS $$
DECLARE
    cur1 REFCURSOR := 'cur_employees';
    cur2 REFCURSOR := 'cur_salaries';
BEGIN
    -- 첫 번째 결과 집합
    OPEN cur1 FOR
        SELECT emp_id, emp_name, dept_id
        FROM employees
        WHERE dept_id = p_dept_id;

    -- 항상 두 번째 커서도 열어서 반환 (조건 없이)
    OPEN cur2 FOR
        SELECT emp_id, salary, bonus
        FROM salaries
        WHERE dept_id = p_dept_id;
        -- 결과가 없으면 빈 결과 집합을 반환하므로 02001 방지
END;
$$;

-- 프로시저 호출 및 결과 확인
BEGIN;
CALL get_employee_data_fixed(10);
FETCH ALL FROM cur_employees;
FETCH ALL FROM cur_salaries;
COMMIT;

원인 2 해결: 커서 상태를 확인한 후 Fetch 수행

커서가 열려 있는지 확인한 뒤 데이터를 가져오도록 로직을 보완합니다.

-- 커서 상태 확인 후 안전하게 Fetch하는 예시
DO $$
DECLARE
    cur REFCURSOR;
    rec RECORD;
    cur_name TEXT := 'safe_cursor';
BEGIN
    -- 커서 열기
    OPEN cur FOR
        SELECT emp_id, emp_name FROM employees WHERE dept_id = 10;

    -- 루프로 안전하게 Fetch (더 이상 없으면 자동 종료)
    LOOP
        FETCH cur INTO rec;
        EXIT WHEN NOT FOUND;  -- 02001 방지: FOUND 체크
        RAISE NOTICE 'Employee: % - %', rec.emp_id, rec.emp_name;
    END LOOP;

    -- 명시적으로 커서 닫기
    CLOSE cur;

    -- 이미 닫힌 커서에 Fetch 시도하지 않음
    -- (아래 줄은 에러를 유발하므로 절대 실행 금지)
    -- FETCH cur INTO rec;  -- 02001 발생 가능!

EXCEPTION
    WHEN no_data_found THEN
        RAISE NOTICE '더 이상 반환할 결과 집합이 없습니다 (02001 방지됨).';
END;
$$;

원인 3 해결: 드라이버 측 결과 순회 로직 수정 (JDBC 예시)

Java 애플리케이션에서 JDBC를 사용하는 경우, getMoreResults() 반환값을 반드시 체크해야 합니다.

-- PostgreSQL 함수: 다중 결과 집합 반환
CREATE OR REPLACE FUNCTION get_multi_results()
RETURNS SETOF REFCURSOR
LANGUAGE plpgsql
AS $$
DECLARE
    ref1 REFCURSOR := 'result_set_1';
    ref2 REFCURSOR := 'result_set_2';
BEGIN
    OPEN ref1 FOR SELECT * FROM employees LIMIT 10;
    RETURN NEXT ref1;

    OPEN ref2 FOR SELECT * FROM departments;
    RETURN NEXT ref2;
END;
$$;

-- 테스트: 반환된 커서를 순서대로 Fetch
BEGIN;

SELECT get_multi_results();

-- 첫 번째 결과 집합
FETCH ALL FROM result_set_1;

-- 두 번째 결과 집합
FETCH ALL FROM result_set_2;

COMMIT;

예방 방법

1. 프로시저의 결과 집합 반환을 항상 명시적이고 일관되게 유지하라

저장 프로시저를 작성할 때 조건 분기에 관계없이 선언된 수만큼의 커서를 반드시 열도록 설계하세요. 특정 조건에서 반환할 데이터가 없더라도 빈 결과 집합(empty result set)을 반환하는 것이 02001 에러를 예방하는 가장 효과적인 방법입니다. 또한, 프로시저 시그니처와 실제 구현 사이의 불일치를 주기적으로 리뷰하는 코드 감사(Code Audit) 프로세스를 팀 내에 도입하는 것을 권장합니다.

2. 애플리케이션 레이어에서 결과 집합 소비 시 반드시 경계 체크(Boundary Check)를 수행하라

JDBC, ODBC, psycopg2 등 어떤 드라이버를 사용하든 간에, 다음 결과 집합이 존재하는지 확인하는 로직(getMoreResults() 반환값 체크, FOUND 변수 검사 등)을 항상 포함하세요. 결과 집합의 수를 하드코딩하는 것보다 루프와 종료 조건을 활용하는 방어적 프로그래밍 패턴이 장기적으로 안전합니다. 드라이버 버전 업그레이드 후에는 반드시 회귀 테스트(Regression Test)를 실행하여 다중 결과 집합 처리 로직에 이상이 없는지 확인하세요.


관련 에러

  • 02000 (no_data_found): 가장 인접한 에러로, SELECT INTO나 FETCH 결과가 없을 때 발생합니다. 02001과 달리 단일 쿼리 수준에서 발생하는 점이 다릅니다.
  • 34000 (invalid_cursor_name): 존재하지 않는 커서 이름으로 접근할 때 발생하며, 커서 기반 프로시저에서 02001과 함께 혼동되기 쉽습니다.
  • 24000 (invalid_cursor_state): 이미 닫혔거나 유효하지 않은 커서 상태에서 작업을 시도할 때 발생하며, 커서를 닫은 후 Fetch를 재시도하는 시나리오에서 02001과 연계하여 나타날 수 있습니다.
  • P0002 (no_data_found in PL/pgSQL): PL/pgSQL 블록에서 SELECT INTO 구문이 아무 행도 반환하지 않을 때 발생하는 PostgreSQL 고유 에러로, 동적 결과 집합 부재와 유사한 맥락에서 디버깅 시 함께 검토해야 합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기