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

P0002
2026년 10월 02일 | DBMS Error 가이드

이 글에서 다루는 내용

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

P0002 no data found 는?

PostgreSQL 에러 코드 P0002 (NO_DATA_FOUND) 는 PL/pgSQL 블록 내에서 SELECT INTO 또는 FETCH 구문을 실행했을 때 결과 행이 단 하나도 반환되지 않는 경우에 발생하는 예외입니다. 일반적인 SQL 쿼리와 달리, PL/pgSQL 절차적 코드에서는 데이터가 없을 때 이를 명시적으로 처리하지 않으면 런타임 에러로 이어질 수 있습니다. 이 에러는 특히 복잡한 비즈니스 로직을 담은 저장 프로시저나 함수에서 자주 발생하며, 운영 환경에서 예상치 못한 장애를 유발할 수 있으므로 반드시 올바르게 처리해야 합니다.


주요 발생 원인

1. SELECT INTO 구문에서 결과가 없는 경우

PL/pgSQL에서 SELECT INTO variable 구문은 정확히 한 행의 결과를 변수에 담기 위해 사용됩니다. 만약 해당 쿼리가 아무 행도 반환하지 않는데 STRICT 옵션을 사용하고 있다면, PostgreSQL은 즉시 P0002 예외를 발생시킵니다. 특히 운영 데이터가 삭제되거나 필터 조건이 너무 엄격하게 설정된 경우 이 상황이 빈번히 발생합니다.

2. 커서(CURSOR) FETCH 후 데이터 없음

커서를 열고 FETCH 명령으로 데이터를 읽을 때, 커서가 더 이상 반환할 행이 없는 상태에서 예외 처리 없이 계속 FETCH를 시도하면 에러가 발생할 수 있습니다. 특히 반복문(LOOP) 안에서 커서를 사용할 때 종료 조건을 제대로 설정하지 않으면 이 문제가 생깁니다. FOUND 특수 변수를 확인하지 않는 것이 대표적인 실수입니다.

3. 잘못된 기본 키 또는 외래 키 조회

애플리케이션 레이어에서 전달된 ID 값이 실제 데이터베이스에 존재하지 않는 경우, 해당 키로 단건 조회를 시도하는 함수에서 P0002가 발생합니다. 데이터가 이미 삭제되었거나, 트랜잭션 격리 수준 차이로 인해 다른 세션에서 삭제된 데이터를 조회하는 경우가 이에 해당합니다. 이 경우 애플리케이션과 데이터베이스 간의 데이터 정합성 문제로 이어질 수 있습니다.


해결 방법

원인 1 해결: STRICT 옵션 제거 및 FOUND 변수 활용

SELECT INTO에서 STRICT를 제거하고, 내장 변수 FOUND를 사용해 결과 유무를 확인하는 방식이 가장 안전합니다.

-- 문제가 되는 코드 (STRICT 사용)
CREATE OR REPLACE FUNCTION get_user_by_id(p_user_id INT)
RETURNS TEXT AS $$
DECLARE
    v_username TEXT;
BEGIN
    SELECT username INTO STRICT v_username
    FROM users
    WHERE user_id = p_user_id;
    -- 데이터가 없으면 P0002 발생!
    RETURN v_username;
END;
$$ LANGUAGE plpgsql;

-- 해결된 코드: FOUND 변수로 결과 유무 확인
CREATE OR REPLACE FUNCTION get_user_by_id(p_user_id INT)
RETURNS TEXT AS $$
DECLARE
    v_username TEXT;
BEGIN
    SELECT username INTO v_username
    FROM users
    WHERE user_id = p_user_id;

    IF NOT FOUND THEN
        -- 데이터가 없을 때 기본값 반환 또는 사용자 정의 예외 처리
        RAISE NOTICE '사용자 ID %를 찾을 수 없습니다.', p_user_id;
        RETURN NULL;
    END IF;

    RETURN v_username;
END;
$$ LANGUAGE plpgsql;

원인 1 해결 (대안): EXCEPTION 블록으로 P0002 직접 처리

STRICT를 유지하면서도 EXCEPTION 블록으로 에러를 안전하게 처리할 수 있습니다.

CREATE OR REPLACE FUNCTION get_user_strict(p_user_id INT)
RETURNS TEXT AS $$
DECLARE
    v_username TEXT;
BEGIN
    SELECT username INTO STRICT v_username
    FROM users
    WHERE user_id = p_user_id;

    RETURN v_username;

EXCEPTION
    WHEN NO_DATA_FOUND THEN
        -- P0002 예외 처리
        RAISE WARNING 'user_id=% 에 해당하는 사용자가 존재하지 않습니다.', p_user_id;
        RETURN NULL;
    WHEN TOO_MANY_ROWS THEN
        -- P0003 예외 처리도 함께
        RAISE WARNING '복수의 사용자가 존재합니다. user_id=%', p_user_id;
        RETURN NULL;
END;
$$ LANGUAGE plpgsql;

원인 2 해결: 커서 FETCH 시 FOUND 변수 체크

CREATE OR REPLACE FUNCTION process_orders()
RETURNS VOID AS $$
DECLARE
    cur CURSOR FOR SELECT order_id, amount FROM orders WHERE status = 'PENDING';
    v_order_id  INT;
    v_amount    NUMERIC;
BEGIN
    OPEN cur;

    LOOP
        FETCH cur INTO v_order_id, v_amount;

        -- FOUND가 FALSE이면 더 이상 데이터 없음 → 루프 탈출
        EXIT WHEN NOT FOUND;

        -- 실제 비즈니스 로직 처리
        RAISE NOTICE '주문 처리 중: order_id=%, amount=%', v_order_id, v_amount;
        UPDATE orders SET status = 'PROCESSED' WHERE order_id = v_order_id;
    END LOOP;

    CLOSE cur;
    RAISE NOTICE '모든 주문 처리 완료';
END;
$$ LANGUAGE plpgsql;

원인 3 해결: 존재 여부를 먼저 확인하는 패턴

CREATE OR REPLACE FUNCTION safe_delete_user(p_user_id INT)
RETURNS BOOLEAN AS $$
DECLARE
    v_exists BOOLEAN;
BEGIN
    -- 먼저 존재 여부 확인
    SELECT EXISTS (
        SELECT 1 FROM users WHERE user_id = p_user_id
    ) INTO v_exists;

    IF NOT v_exists THEN
        RAISE NOTICE '삭제 대상 사용자(%)가 존재하지 않습니다.', p_user_id;
        RETURN FALSE;
    END IF;

    -- 존재하는 경우에만 삭제 수행
    DELETE FROM users WHERE user_id = p_user_id;
    RAISE NOTICE '사용자(%)가 성공적으로 삭제되었습니다.', p_user_id;
    RETURN TRUE;
END;
$$ LANGUAGE plpgsql;

예방 방법

1. 모든 PL/pgSQL 함수에 표준 EXCEPTION 블록 템플릿 적용

팀 내에서 PL/pgSQL 함수를 작성할 때 반드시 NO_DATA_FOUND 및 TOO_MANY_ROWS 예외를 포함한 표준 EXCEPTION 블록을 함수 템플릿으로 강제 적용하세요. 코드 리뷰 체크리스트에 “SELECT INTO STRICT 사용 시 EXCEPTION 블록 존재 여부”를 항목으로 추가하면 실수를 사전에 방지할 수 있습니다. 또한 pgTAP 같은 테스트 프레임워크를 활용해 빈 결과셋 시나리오를 포함한 단위 테스트를 작성하면 배포 전에 문제를 발견할 수 있습니다.

2. 데이터 정합성 모니터링 및 방어적 쿼리 패턴 사용

운영 환경에서는 애플리케이션이 전달하는 ID 값이 항상 유효하다고 가정하지 마세요. COALESCE나 EXISTS 서브쿼리를 활용한 방어적 쿼리 패턴을 도입하고, PostgreSQL 로그에서 P0002 에러 발생 빈도를 모니터링하여 데이터 품질 문제를 조기에 감지하세요. log_min_messages 설정을 활용하거나 Datadog, pgBadger 같은 모니터링 도구로 에러 패턴을 추적하는 것을 권장합니다.


관련 에러

  • P0001 (RAISE_EXCEPTION): 사용자가 명시적으로 RAISE EXCEPTION을 호출할 때 발생하며, NO_DATA_FOUND와 함께 PL/pgSQL 예외 처리의 핵심 에러입니다.
  • P0003 (TOO_MANY_ROWS): SELECT INTO STRICT 구문에서 두 개 이상의 행이 반환될 때 발생하는 에러로, P0002와 반대 상황입니다. 두 에러를 항상 함께 처리하는 것이 좋습니다.
  • 02000 (NO_DATA): SQL 표준 레벨의 “데이터 없음” 상태 코드로, P0002와 유사하지만 커서 작업에서 주로 참조됩니다.
  • 23503 (FOREIGN_KEY_VIOLATION): 연관된 부모 레코드가 없을 때 발생하며, P0002와 함께 데이터 무결성 관련 에러로 묶어서 처리 전략을 수립해야 합니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기