2026년 10월 06일 | DBMS Error 가이드
이 글에서 다루는 내용
02000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
02000 no data 는?
PostgreSQL 에러 코드 02000 (no_data)는 SQL 쿼리나 커서(cursor) 작업에서 예상했던 데이터가 존재하지 않을 때 발생하는 경고성 상태 코드입니다. 주로 SELECT INTO, FETCH, 또는 PL/pgSQL 함수 내부에서 결과 행이 없을 때 트리거되며, 일반적인 에러라기보다는 “데이터 없음” 상태를 알리는 신호입니다. 실무에서는 PL/pgSQL 블록 내에서 FOUND 변수 또는 예외 처리 로직이 제대로 구현되지 않았을 때 예기치 않은 동작을 유발할 수 있어 주의가 필요합니다.
주요 발생 원인
1. PL/pgSQL 함수 내 SELECT INTO에서 데이터 미존재
PL/pgSQL 함수 안에서 SELECT INTO 구문을 사용할 때, 조건에 맞는 행이 없으면 해당 변수는 NULL로 설정되고 FOUND 변수는 false가 됩니다. 이 상태를 명시적으로 처리하지 않으면, 이후 해당 변수를 참조하는 로직에서 NULL 관련 오류나 잘못된 결과가 발생할 수 있습니다. 특히 STRICT 옵션을 함께 사용하면 데이터가 없을 때 NO_DATA_FOUND 예외가 명시적으로 발생하므로, 이를 적절히 핸들링하는 것이 중요합니다.
2. FETCH 커서에서 더 이상 읽을 행이 없는 경우
명시적 커서(explicit cursor)를 사용하는 루프나 단일 FETCH 명령에서 커서가 결과셋의 끝에 도달했을 때 no data 상태가 발생합니다. 커서 반복문에서 루프 종료 조건 없이 FETCH를 계속 시도하면 이 상태가 반복적으로 발생할 수 있으며, 이는 PL/pgSQL 루프 설계 오류에서 비롯되는 경우가 많습니다. LOOP보다 FOR ... IN 형태의 커서 루프를 사용하면 이 문제를 자연스럽게 회피할 수 있습니다.
3. 애플리케이션 레벨에서의 잘못된 결과 처리
Java, Python 등 외부 애플리케이션에서 PostgreSQL 결과셋을 처리할 때, 쿼리 결과가 0건인 상황을 에러로 혼동하여 예외 처리 로직이 잘못 동작하는 경우가 있습니다. JDBC나 psycopg2 같은 드라이버에서 fetchone() 또는 rs.next()가 None 또는 false를 반환하는데, 이를 적절히 검사하지 않으면 NullPointerException이나 AttributeError 같은 애플리케이션 에러로 전파됩니다. 이는 직접적인 PostgreSQL 에러라기보다는 드라이버 레벨의 처리 누락에서 비롯됩니다.
해결 방법
원인 1 해결 – SELECT INTO 후 FOUND 변수 확인
STRICT 없이 사용하는 경우, FOUND 변수를 통해 데이터 존재 여부를 검사합니다.
DO $$
DECLARE
v_employee_name TEXT;
v_salary NUMERIC;
BEGIN
SELECT emp_name, salary
INTO v_employee_name, v_salary
FROM employees
WHERE emp_id = 9999; -- 존재하지 않는 ID
IF NOT FOUND THEN
RAISE NOTICE '해당 직원 데이터를 찾을 수 없습니다. emp_id: 9999';
-- 기본값 처리 또는 조기 리턴
RETURN;
END IF;
RAISE NOTICE '직원명: %, 급여: %', v_employee_name, v_salary;
END;
$$;
STRICT 옵션을 사용하면 NO_DATA_FOUND 예외를 명시적으로 캐치할 수 있습니다.
DO $$
DECLARE
v_employee_name TEXT;
v_salary NUMERIC;
BEGIN
SELECT emp_name, salary
INTO STRICT v_employee_name, v_salary
FROM employees
WHERE emp_id = 9999;
RAISE NOTICE '직원명: %, 급여: %', v_employee_name, v_salary;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE WARNING '데이터 없음: emp_id 9999에 해당하는 직원이 없습니다.';
WHEN TOO_MANY_ROWS THEN
RAISE WARNING '복수 행 반환: 조건을 더 구체적으로 지정하세요.';
END;
$$;
원인 2 해결 – 커서 FETCH 처리 개선
LOOP와 FETCH를 함께 사용할 때 FOUND 변수로 루프 종료를 제어합니다.
DO $$
DECLARE
cur_emp CURSOR FOR
SELECT emp_id, emp_name, salary
FROM employees
WHERE department = 'IT'
ORDER BY emp_id;
v_emp_id INT;
v_emp_name TEXT;
v_salary NUMERIC;
BEGIN
OPEN cur_emp;
LOOP
FETCH cur_emp INTO v_emp_id, v_emp_name, v_salary;
-- FETCH 후 반드시 FOUND 체크
EXIT WHEN NOT FOUND;
RAISE NOTICE 'ID: %, 이름: %, 급여: %',
v_emp_id, v_emp_name, v_salary;
END LOOP;
CLOSE cur_emp;
RAISE NOTICE '커서 처리 완료';
END;
$$;
더 권장되는 방법은 FOR 루프를 활용하는 것으로, 커서를 명시적으로 열고 닫는 과정 없이 안전하게 사용할 수 있습니다.
DO $$
DECLARE
rec RECORD;
v_count INT := 0;
BEGIN
FOR rec IN
SELECT emp_id, emp_name, salary
FROM employees
WHERE department = 'IT'
ORDER BY emp_id
LOOP
v_count := v_count + 1;
RAISE NOTICE 'ID: %, 이름: %, 급여: %',
rec.emp_id, rec.emp_name, rec.salary;
END LOOP;
IF v_count = 0 THEN
RAISE NOTICE 'IT 부서에 해당하는 직원이 없습니다.';
ELSE
RAISE NOTICE '총 % 명의 직원을 처리했습니다.', v_count;
END IF;
END;
$$;
원인 3 해결 – 애플리케이션 레벨 처리 (Python 예시)
-- 테스트용 쿼리 (결과가 없는 경우)
SELECT emp_id, emp_name
FROM employees
WHERE emp_id = 99999;
Python (psycopg2) 애플리케이션에서의 올바른 처리:
-- 실무에서 자주 쓰는 안전한 조회 패턴
-- 결과가 없을 경우 기본값 반환하는 래퍼 함수
CREATE OR REPLACE FUNCTION get_employee_safe(p_emp_id INT)
RETURNS TABLE(emp_id INT, emp_name TEXT, salary NUMERIC) AS $$
BEGIN
RETURN QUERY
SELECT e.emp_id, e.emp_name, e.salary
FROM employees e
WHERE e.emp_id = p_emp_id;
-- 결과가 없어도 예외 발생하지 않음
-- 빈 결과셋 반환
END;
$$ LANGUAGE plpgsql;
-- 사용 예시
SELECT * FROM get_employee_safe(99999);
-- 결과: 0 rows (에러 없이 처리)
예방 방법
1. PL/pgSQL 함수 작성 시 항상 FOUND 변수 또는 예외 처리 포함
모든 SELECT INTO 구문 이후에는 반드시 IF NOT FOUND THEN 블록을 추가하거나, INTO STRICT를 사용하여 NO_DATA_FOUND 예외를 명시적으로 처리하는 것을 팀 내 코딩 컨벤션으로 정립하세요. 코드 리뷰 체크리스트에 이 항목을 포함시켜, 데이터 없음 상황이 묵시적으로 넘어가는 일이 없도록 합니다.
-- 팀 표준 함수 템플릿 예시
CREATE OR REPLACE FUNCTION find_employee(p_emp_id INT)
RETURNS JSON AS $$
DECLARE
v_result JSON;
BEGIN
SELECT json_build_object(
'emp_id', emp_id,
'emp_name', emp_name,
'salary', salary
)
INTO STRICT v_result
FROM employees
WHERE emp_id = p_emp_id;
RETURN v_result;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- 명시적 NULL 반환 또는 기본 JSON 반환
RETURN json_build_object('error', 'employee not found', 'emp_id', p_emp_id);
WHEN TOO_MANY_ROWS THEN
RETURN json_build_object('error', 'multiple employees found', 'emp_id', p_emp_id);
END;
$$ LANGUAGE plpgsql;
2. pgTAP 또는 단위 테스트로 빈 결과셋 시나리오 검증
데이터가 없는 케이스는 실제 운영 환경에서 예상치 못하게 발생하는 경우가 많으므로, 개발 단계부터 빈 결과셋 시나리오를 테스트 케이스에 포함시켜야 합니다. pgTAP 프레임워크나 간단한 DO 블록을 활용하여 함수가 데이터 없음 상황에서도 예외 없이 정상 동작하는지 사전에 검증하는 습관을 들이세요.
-- 간단한 검증 스크립트 예시
DO $$
DECLARE
v_result JSON;
BEGIN
-- 존재하지 않는 ID로 테스트
v_result := find_employee(99999);
ASSERT v_result->>'error' IS NOT NULL,
'에러 케이스에서 error 필드가 반환되어야 합니다.';
-- 정상 케이스 테스트 (사전에 데이터 삽입 필요)
v_result := find_employee(1);
ASSERT v_result->>'emp_id' IS NOT NULL,
'정상 케이스에서 emp_id가 반환되어야 합니다.';
RAISE NOTICE '모든 테스트 통과';
END;
$$;
관련 에러
P0002(NO_DATA_FOUND):02000의 PL/pgSQL 예외 버전으로,SELECT INTO STRICT에서 결과가 없을 때 발생합니다.EXCEPTION WHEN NO_DATA_FOUND블록으로 처리합니다.P0003(TOO_MANY_ROWS):SELECT INTO STRICT에서 두 개 이상의 행이 반환될 때 발생하며,NO_DATA_FOUND와 함께 쌍으로 처리해야 합니다.02001(no_additional_dynamic_result_sets_returned): 동적 SQL 실행에서 추가 결과셋이 없을 때 발생하는 유사 상태 코드입니다.23000(integrity_constraint_violation): 데이터 없음과는 반대로, 잘못된 참조 데이터로 인해 제약조건이 위반될 때 발생하며, 두 에러를 함께 방어적으로 처리해야 하는 경우가 많습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.