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

0100C
2026년 10월 04일 | DBMS Error 가이드

이 글에서 다루는 내용

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

0100C dynamic result sets returned 는?

PostgreSQL 에러 코드 0100C는 dynamic_result_sets_returned 경고(Warning)로, SQL/PSM 표준에서 저장 프로시저(Stored Procedure)가 호출자에게 반환하도록 선언된 것보다 더 많은 동적 결과 집합(Result Set)을 반환할 때 발생합니다. 이 에러는 실제로 실행을 중단시키는 오류(ERROR)가 아니라 경고(WARNING) 수준의 메시지로, 프로시저가 예상보다 많은 커서 또는 결과 집합을 반환하는 상황에서 PostgreSQL이 클라이언트에게 알리는 신호입니다. 주로 CALL 구문으로 프로시저를 호출하거나, JDBC/ODBC 같은 드라이버를 통해 다중 결과 집합을 처리하는 환경에서 마주치게 됩니다.


주요 발생 원인

  • 저장 프로시저 내 선언되지 않은 추가 커서 반환

저장 프로시저를 정의할 때 DYNAMIC RESULT SETS 절에 명시한 숫자보다 실제 실행 시 더 많은 커서를 열어 반환하는 경우가 가장 흔한 원인입니다. SQL 표준에서는 프로시저가 반환할 결과 집합의 최대 개수를 선언해야 하며, 이를 초과하면 PostgreSQL은 0100C 경고를 발생시킵니다. 특히 조건 분기(IF/CASE)에 따라 동적으로 커서 수가 달라지는 프로시저에서 자주 발생합니다.

  • 레거시 코드 또는 다른 DBMS에서 마이그레이션된 프로시저

Oracle, MS SQL Server, DB2 등에서 PostgreSQL로 마이그레이션된 저장 프로시저는 원본 DBMS의 결과 집합 처리 방식이 PostgreSQL과 다르기 때문에 이 경고가 발생하기 쉽습니다. 다른 DBMS에서는 암묵적으로 다수의 결과 집합을 반환하는 것이 허용되지만, PostgreSQL은 이를 명시적으로 선언하도록 요구합니다. 마이그레이션 도구가 자동으로 변환하지 못하는 엣지 케이스가 특히 문제가 됩니다.

  • 애플리케이션 레이어에서의 잘못된 프로시저 호출 설계

JDBC나 psycopg2 같은 드라이버를 통해 저장 프로시저를 호출할 때, 드라이버가 내부적으로 여러 결과 집합을 처리하는 방식이 PostgreSQL의 기대치와 맞지 않는 경우에도 이 경고가 발생할 수 있습니다. 특히 하나의 프로시저 안에서 여러 SELECT 문을 실행하고 각각의 결과를 별도의 커서로 반환하는 설계는 이 경고를 유발하는 대표적인 패턴입니다. 애플리케이션과 데이터베이스 프로시저 간의 계약(Contract)이 명확하지 않을 때 주로 나타납니다.


해결 방법

원인 1 해결: DYNAMIC RESULT SETS 선언 수정

프로시저 정의 시 실제로 반환하는 결과 집합 수와 선언을 일치시켜야 합니다.

-- 문제가 있는 프로시저 예시: 커서 2개를 반환하지만 1개만 선언
CREATE OR REPLACE PROCEDURE get_employee_data(
    INOUT ref1 refcursor,
    INOUT ref2 refcursor
)
LANGUAGE plpgsql
AS $$
BEGIN
    -- 첫 번째 결과 집합: 직원 목록
    OPEN ref1 FOR
        SELECT emp_id, emp_name, department
        FROM employees
        WHERE active = true;

    -- 두 번째 결과 집합: 부서별 집계
    OPEN ref2 FOR
        SELECT department, COUNT(*) AS emp_count
        FROM employees
        WHERE active = true
        GROUP BY department;
END;
$$;

-- 올바른 호출 방법: 트랜잭션 내에서 커서 사용
BEGIN;

CALL get_employee_data('ref1', 'ref2');

-- 첫 번째 커서 결과 가져오기
FETCH ALL FROM ref1;

-- 두 번째 커서 결과 가져오기
FETCH ALL FROM ref2;

COMMIT;

원인 2 해결: 마이그레이션된 프로시저 재작성

Oracle 스타일의 다중 결과 집합 반환 프로시저를 PostgreSQL 방식으로 변환합니다.

-- Oracle 스타일에서 PostgreSQL 스타일로 변환된 프로시저
CREATE OR REPLACE PROCEDURE get_sales_report(
    IN p_year INTEGER,
    INOUT cur_monthly refcursor,
    INOUT cur_product  refcursor,
    INOUT cur_region   refcursor
)
LANGUAGE plpgsql
AS $$
BEGIN
    -- 월별 매출 결과 집합
    OPEN cur_monthly FOR
        SELECT
            EXTRACT(MONTH FROM sale_date) AS month,
            SUM(amount) AS total_sales
        FROM sales
        WHERE EXTRACT(YEAR FROM sale_date) = p_year
        GROUP BY month
        ORDER BY month;

    -- 제품별 매출 결과 집합
    OPEN cur_product FOR
        SELECT
            product_id,
            product_name,
            SUM(amount) AS product_sales
        FROM sales
        JOIN products USING (product_id)
        WHERE EXTRACT(YEAR FROM sale_date) = p_year
        GROUP BY product_id, product_name
        ORDER BY product_sales DESC;

    -- 지역별 매출 결과 집합
    OPEN cur_region FOR
        SELECT
            region,
            SUM(amount) AS region_sales
        FROM sales
        WHERE EXTRACT(YEAR FROM sale_date) = p_year
        GROUP BY region
        ORDER BY region_sales DESC;
END;
$$;

-- 호출 예시
BEGIN;
CALL get_sales_report(2024, 'monthly_cur', 'product_cur', 'region_cur');
FETCH ALL FROM monthly_cur;
FETCH ALL FROM product_cur;
FETCH ALL FROM region_cur;
COMMIT;

원인 3 해결: 단일 결과 집합으로 리팩토링

가능하다면 여러 결과 집합을 하나로 통합하거나, 각각의 용도에 맞는 별도의 함수로 분리합니다.

-- 여러 결과 집합 대신 JSON으로 통합 반환하는 방법
CREATE OR REPLACE FUNCTION get_dashboard_data(p_dept_id INTEGER)
RETURNS JSON
LANGUAGE plpgsql
AS $$
DECLARE
    v_employees JSON;
    v_stats     JSON;
    v_result    JSON;
BEGIN
    -- 직원 목록
    SELECT json_agg(row_to_json(e))
    INTO v_employees
    FROM (
        SELECT emp_id, emp_name, hire_date, salary
        FROM employees
        WHERE dept_id = p_dept_id
        ORDER BY emp_name
    ) e;

    -- 부서 통계
    SELECT row_to_json(s)
    INTO v_stats
    FROM (
        SELECT
            COUNT(*)           AS total_emp,
            AVG(salary)        AS avg_salary,
            MAX(salary)        AS max_salary,
            MIN(salary)        AS min_salary
        FROM employees
        WHERE dept_id = p_dept_id
    ) s;

    -- JSON으로 통합
    v_result := json_build_object(
        'employees', v_employees,
        'statistics', v_stats
    );

    RETURN v_result;
END;
$$;

-- 사용 예시
SELECT get_dashboard_data(10);

-- 또는 여러 함수로 명확하게 분리
CREATE OR REPLACE FUNCTION get_dept_employees(p_dept_id INTEGER)
RETURNS TABLE(emp_id INT, emp_name TEXT, salary NUMERIC)
LANGUAGE sql
AS $$
    SELECT emp_id, emp_name, salary
    FROM employees
    WHERE dept_id = p_dept_id;
$$;

CREATE OR REPLACE FUNCTION get_dept_stats(p_dept_id INTEGER)
RETURNS TABLE(total_emp BIGINT, avg_salary NUMERIC)
LANGUAGE sql
AS $$
    SELECT COUNT(*), AVG(salary)
    FROM employees
    WHERE dept_id = p_dept_id;
$$;

예방 방법

  • 프로시저 설계 시 결과 집합 수를 명시적으로 문서화하고 테스트하라

저장 프로시저를 개발할 때부터 반환할 결과 집합의 수와 각 커서의 구조를 명확히 정의하고, 단위 테스트(Unit Test)에 커서 반환 수 검증 로직을 포함시키는 것이 중요합니다. 특히 CI/CD 파이프라인에 pgTAP 같은 PostgreSQL 테스트 프레임워크를 통합하면, 프로시저 변경 시 결과 집합 수가 의도치 않게 변경되는 것을 사전에 방지할 수 있습니다. 아래처럼 코드 리뷰 체크리스트에 “반환 커서 수 확인” 항목을 추가하는 것을 권장합니다.

“`sql

— pgTAP을 이용한 프로시저 결과 집합 수 테스트 예시

SELECT plan(1);

BEGIN;

CALL get_employee_data(‘c1’, ‘c2’);

SELECT is(

(SELECT COUNT(*) FROM pg_cursors WHERE name IN (‘c1’, ‘c2’)),

2::BIGINT,

‘프로시저가 정확히 2개의 커서를 반환해야 합니다’

);

ROLLBACK;

SELECT finish();

“`

  • 다중 결과 집합보다 RETURNS TABLE 또는 JSON 반환 방식을 선호하라

PostgreSQL에서 다중 결과 집합(refcursor 여러 개)을 반환하는 설계는 유지보수 난이도를 높이고 0100C 같은 경고를 유발하기 쉽습니다. 가능하면 RETURNS TABLE(...) 또는 RETURNS SETOF를 사용하는 함수(Function) 방식이나, 복합 데이터를 JSON/JSONB로 묶어 단일 값으로 반환하는 패턴을 채택하면 훨씬 명확하고 안전한 인터페이스를 제공할 수 있습니다. 이 방식은 ORM 라이브러리나 JDBC 드라이버와의 호환성도 높습니다.


관련 에러

  • 01000 WARNING: 일반적인 경고(Warning) 메시지로, 0100C의 상위 카테고리에 해당합니다.
  • 0100D CURSOR ALREADY OPEN: 이미 열려 있는 커서를 다시 열려고 할 때 발생하며, 다중 결과 집합 처리 로직에서 함께 나타나는 경우가 많습니다.
  • 34000 INVALID CURSOR NAME: 존재하지 않는 커서 이름을 참조할 때 발생하며, refcursor 기반 프로시저에서 FETCH 시 커서 이름을 잘못 지정한 경우 마주치게 됩니다.
  • 02000 NO DATA FOUND: 커서 FETCH 시 더 이상 가져올 데이터가 없을 때 발생하며, 다중 결과 집합을 순차적으로 처리할 때 종료 조건으로 활용됩니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기