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

02000
2026년 05월 29일 | DBMS Error 가이드

이 글에서 다루는 내용

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

02000 no data 는?

PostgreSQL 에러 코드 02000은 SQLSTATE 표준에서 “no data”를 의미하며, 쿼리 또는 커서 작업의 결과로 반환된 행이 없을 때 발생합니다. 이 에러는 PL/pgSQL 함수나 저장 프로시저 내에서 SELECT INTO, FETCH, 또는 단일 행을 기대하는 구문이 아무런 결과를 반환하지 못했을 때 주로 나타납니다. 심각한 데이터 손상이나 시스템 장애를 의미하지는 않지만, 비즈니스 로직에서 예상치 못한 빈 결과를 처리하지 않으면 애플리케이션이 잘못된 방향으로 동작할 수 있습니다.


주요 발생 원인

1. PL/pgSQL에서 SELECT INTO로 존재하지 않는 데이터 조회

PL/pgSQL 블록 안에서 SELECT INTO 구문을 사용할 때, 조건에 맞는 행이 없으면 대상 변수에 NULL이 할당되고 FOUND 변수가 false로 설정됩니다. 만약 이후 로직에서 FOUND 상태를 확인하지 않고 변수를 사용하면 의도하지 않은 동작이 발생하거나, STRICT 옵션을 사용한 경우 명시적으로 02000 에러가 발생합니다.

2. 커서(CURSOR) FETCH 작업 중 더 이상 행이 없는 경우

커서를 이용해 결과 집합을 순회할 때, 모든 행을 다 읽은 후에도 추가로 FETCH를 시도하면 no data 상태가 됩니다. 루프 종료 조건을 명확히 설정하지 않거나 FOUND 변수를 체크하지 않으면 이 상태가 예외로 이어질 수 있습니다.

3. PERFORM 또는 함수 호출 후 결과 없음

PL/pgSQL에서 함수 결과를 무시하기 위해 PERFORM을 사용하거나, 반환값이 있는 함수를 호출했는데 내부적으로 아무 행도 반환하지 않는 경우 no data 조건이 발생할 수 있습니다. 특히 SETOF 타입을 반환하는 함수에서 결과가 비어있을 때 이를 처리하는 로직이 없으면 문제가 됩니다.


해결 방법

원인 1 해결: SELECT INTOFOUND 확인 및 예외 처리

STRICT 없이 SELECT INTO를 사용하고 반드시 FOUND를 체크하는 습관을 들이는 것이 중요합니다.

DO $$
DECLARE
    v_user_name TEXT;
    v_user_id   INT := 9999; -- 존재하지 않는 ID
BEGIN
    -- STRICT 없이 SELECT INTO 사용
    SELECT username
    INTO v_user_name
    FROM users
    WHERE id = v_user_id;

    -- FOUND 변수로 결과 유무 확인
    IF NOT FOUND THEN
        RAISE NOTICE 'User ID %에 해당하는 사용자가 없습니다.', v_user_id;
        -- 기본값 설정 또는 다른 처리 로직
        v_user_name := 'Unknown';
    ELSE
        RAISE NOTICE '사용자 이름: %', v_user_name;
    END IF;
END;
$$;

STRICT 옵션을 사용하는 경우 반드시 예외 처리 블록을 추가해야 합니다.

DO $$
DECLARE
    v_user_name TEXT;
BEGIN
    -- STRICT 사용 시 행이 없으면 NO_DATA_FOUND 예외 발생
    SELECT username
    INTO STRICT v_user_name
    FROM users
    WHERE id = 9999;

    RAISE NOTICE '사용자 이름: %', v_user_name;

EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RAISE NOTICE 'STRICT 모드: 해당 사용자를 찾을 수 없습니다.';
    WHEN TOO_MANY_ROWS THEN
        RAISE NOTICE '여러 행이 반환되었습니다. 조건을 구체화하세요.';
END;
$$;

원인 2 해결: 커서 루프에서 FOUND 또는 EXIT 조건 활용

커서를 사용할 때는 FOR 루프를 활용하거나, FETCH 후 반드시 FOUND를 확인해야 합니다.

DO $$
DECLARE
    cur_orders CURSOR FOR
        SELECT order_id, amount FROM orders WHERE status = 'PENDING';
    v_order_id  INT;
    v_amount    NUMERIC;
BEGIN
    OPEN cur_orders;

    LOOP
        FETCH cur_orders INTO v_order_id, v_amount;

        -- FETCH 후 더 이상 행이 없으면 루프 종료
        EXIT WHEN NOT FOUND;

        RAISE NOTICE '주문 ID: %, 금액: %', v_order_id, v_amount;
        -- 추가 비즈니스 로직 처리
    END LOOP;

    CLOSE cur_orders;
    RAISE NOTICE '모든 PENDING 주문 처리 완료.';
END;
$$;

-- FOR 루프를 사용하면 더 간결하게 처리 가능 (권장)
DO $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT order_id, amount FROM orders WHERE status = 'PENDING'
    LOOP
        RAISE NOTICE '주문 ID: %, 금액: %', rec.order_id, rec.amount;
    END LOOP;
    -- 행이 없어도 루프 자체는 정상 종료됨
END;
$$;

원인 3 해결: 함수 반환값 존재 여부 검증

SETOF 반환 함수 또는 집합 반환 함수를 사용할 때 결과가 없는 경우를 처리합니다.

-- 결과가 없을 수도 있는 함수 호출 예시
CREATE OR REPLACE FUNCTION get_active_products(p_category TEXT)
RETURNS SETOF products AS $$
BEGIN
    RETURN QUERY
    SELECT * FROM products
    WHERE category = p_category AND is_active = TRUE;

    -- 결과가 없어도 함수는 정상 종료 (빈 집합 반환)
END;
$$ LANGUAGE plpgsql;

-- 호출 측에서 결과 유무 처리
DO $$
DECLARE
    v_count INT;
BEGIN
    SELECT COUNT(*) INTO v_count
    FROM get_active_products('ELECTRONICS');

    IF v_count = 0 THEN
        RAISE NOTICE '해당 카테고리에 활성 상품이 없습니다.';
    ELSE
        RAISE NOTICE '활성 상품 수: %', v_count;
    END IF;
END;
$$;

예방 방법

1. STRICT 사용 시 항상 예외 처리 블록 구성

PL/pgSQL 함수나 프로시저에서 SELECT INTO STRICT를 사용할 때는 반드시 EXCEPTION 블록에 NO_DATA_FOUNDTOO_MANY_ROWS를 함께 처리하는 것을 팀 코딩 표준으로 지정하십시오. 코드 리뷰 단계에서 이 패턴이 빠진 경우 반드시 지적하여 일관성을 유지하는 것이 장기적으로 유지보수를 쉽게 만듭니다.

-- 권장 패턴: STRICT + 예외 처리
CREATE OR REPLACE FUNCTION find_employee(p_id INT)
RETURNS TEXT AS $$
DECLARE
    v_name TEXT;
BEGIN
    SELECT full_name INTO STRICT v_name
    FROM employees WHERE employee_id = p_id;
    RETURN v_name;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN NULL; -- 또는 기본값 반환
    WHEN TOO_MANY_ROWS THEN
        RAISE EXCEPTION '직원 ID %에 중복 데이터가 있습니다.', p_id;
END;
$$ LANGUAGE plpgsql;

2. 애플리케이션 레이어에서 빈 결과 집합 처리 로직 표준화

데이터베이스 레이어뿐 아니라 애플리케이션 코드에서도 빈 결과 집합을 항상 명시적으로 처리하는 패턴을 확립하십시오. ORM이나 드라이버에서 반환되는 null 또는 빈 배열에 대해 방어적 프로그래밍을 적용하고, 통합 테스트에서 “데이터 없음” 시나리오를 반드시 포함시켜 배포 전 검증하는 프로세스를 정착시키는 것이 좋습니다.


관련 에러

  • 02001 (no additional dynamic result sets returned): 동적 결과 집합이 더 이상 없을 때 발생하며, 02000과 유사한 맥락에서 사용됩니다.
  • P0002 (no_data_found): PL/pgSQL에서 SELECT INTO STRICT가 행을 찾지 못할 때 발생하는 에러로, 02000의 PL/pgSQL 전용 파생 에러입니다. 실무에서는 02000보다 P0002를 더 자주 마주치게 됩니다.
  • 21000 (cardinality_violation): 단일 행을 기대하는 곳에 여러 행이 반환될 때 발생하며, SELECT INTO STRICT와 함께 TOO_MANY_ROWS(P0003)로 이어집니다. 데이터 존재 여부를 체크할 때 이 두 에러를 항상 함께 고려해야 합니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기