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

20000
2026년 10월 11일 | DBMS Error 가이드

이 글에서 다루는 내용

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

20000 case not found 는?

PostgreSQL 에러 코드 20000은 case not found라는 이름으로, PL/pgSQL의 CASE 문 실행 중 어떤 WHEN 조건에도 일치하지 않는 값이 입력되었을 때 발생하는 런타임 에러입니다. 이 에러는 SQLSTATE 20000으로 분류되며, CASE 표현식이 아닌 CASE 구문(statement) 에서 ELSE 절이 생략된 상태에서 매칭되는 조건이 없을 때 트리거됩니다. 특히 스토어드 프로시저, 함수, 트리거 등 PL/pgSQL 코드 내부에서 빈번하게 나타나며, SQL 레벨의 단순 CASE 표현식과는 동작 방식이 다르므로 주의가 필요합니다.


주요 발생 원인

1. PL/pgSQL CASE 구문에서 ELSE 절 누락

가장 흔한 원인입니다. PL/pgSQL의 CASE 구문(statement) 은 SQL의 CASE 표현식과 달리, ELSE 절이 없을 때 어떤 WHEN 조건도 만족하지 못하면 즉시 case not found 에러를 발생시킵니다. 개발 초기에는 특정 값만 처리하면 된다고 판단하여 ELSE를 생략하는 경우가 많지만, 운영 환경에서 예상치 못한 데이터가 유입되면 이 에러가 터지게 됩니다.

2. 데이터 타입 불일치 또는 예상치 못한 NULL 값 입력

CASE 구문의 비교 대상 변수에 NULL이 들어오거나, 데이터 타입이 암묵적으로 변환되지 않아 어떤 WHEN 절과도 매칭이 되지 않는 경우입니다. PostgreSQL에서 NULL = NULL은 TRUE가 아니라 NULL을 반환하기 때문에, CASE 구문이 NULL 값을 처리하도록 명시적으로 설계되지 않으면 매칭 실패로 이어집니다. 이는 외부 시스템에서 데이터를 수신하거나, 조인 결과에서 NULL이 생성되는 상황에서 특히 자주 발생합니다.

3. 동적으로 증가하는 코드 값에 대한 CASE 구문 미업데이트

비즈니스 로직 확장에 따라 새로운 상태 코드, 분류 코드 등이 테이블에 추가되었음에도 불구하고, 관련 PL/pgSQL 함수나 프로시저의 CASE 구문이 업데이트되지 않은 경우입니다. 예를 들어 주문 상태가 'PENDING', 'SHIPPED', 'DELIVERED'만 처리하도록 코드가 작성되어 있는데, 이후 'CANCELLED'나 'RETURNED' 상태가 추가되면 해당 데이터가 들어올 때마다 에러가 발생합니다. 이는 코드와 데이터 간의 동기화 문제로, 시스템 규모가 클수록 놓치기 쉬운 함정입니다.


해결 방법

해결 1: ELSE 절 반드시 추가

모든 PL/pgSQL CASE 구문에 ELSE 절을 추가하여 예상치 못한 값을 안전하게 처리합니다.

-- 문제 발생 코드 (ELSE 없음)
CREATE OR REPLACE FUNCTION get_order_label(p_status TEXT)
RETURNS TEXT AS $$
DECLARE
    v_label TEXT;
BEGIN
    CASE p_status
        WHEN 'PENDING'   THEN v_label := '대기중';
        WHEN 'SHIPPED'   THEN v_label := '배송중';
        WHEN 'DELIVERED' THEN v_label := '배송완료';
        -- ELSE 없음 → 'CANCELLED' 입력 시 20000 에러 발생!
    END CASE;
    RETURN v_label;
END;
$$ LANGUAGE plpgsql;

-- 해결된 코드 (ELSE 추가)
CREATE OR REPLACE FUNCTION get_order_label(p_status TEXT)
RETURNS TEXT AS $$
DECLARE
    v_label TEXT;
BEGIN
    CASE p_status
        WHEN 'PENDING'   THEN v_label := '대기중';
        WHEN 'SHIPPED'   THEN v_label := '배송중';
        WHEN 'DELIVERED' THEN v_label := '배송완료';
        ELSE
            -- 알 수 없는 상태는 로그를 남기고 기본값 반환
            RAISE WARNING '알 수 없는 주문 상태: %', p_status;
            v_label := '알 수 없음';
    END CASE;
    RETURN v_label;
END;
$$ LANGUAGE plpgsql;

-- 테스트
SELECT get_order_label('CANCELLED');  -- '알 수 없음' 반환 + WARNING 로그
SELECT get_order_label('PENDING');    -- '대기중' 반환

해결 2: NULL 값 명시적 처리

NULL 입력에 대비하여 CASE 구문 앞에 NULL 체크를 추가하거나, WHEN NULL 대신 IS NULL 조건으로 처리합니다.

CREATE OR REPLACE FUNCTION process_category(p_code TEXT)
RETURNS TEXT AS $$
DECLARE
    v_result TEXT;
BEGIN
    -- NULL 값 사전 처리
    IF p_code IS NULL THEN
        RETURN '코드 없음';
    END IF;

    CASE p_code
        WHEN 'A' THEN v_result := '카테고리 A';
        WHEN 'B' THEN v_result := '카테고리 B';
        WHEN 'C' THEN v_result := '카테고리 C';
        ELSE
            v_result := '미정의 카테고리: ' || p_code;
    END CASE;

    RETURN v_result;
END;
$$ LANGUAGE plpgsql;

-- Searched CASE를 사용하여 NULL도 명시적으로 처리하는 방법
CREATE OR REPLACE FUNCTION process_category_v2(p_code TEXT)
RETURNS TEXT AS $$
DECLARE
    v_result TEXT;
BEGIN
    CASE
        WHEN p_code IS NULL    THEN v_result := '코드 없음';
        WHEN p_code = 'A'      THEN v_result := '카테고리 A';
        WHEN p_code = 'B'      THEN v_result := '카테고리 B';
        WHEN p_code = 'C'      THEN v_result := '카테고리 C';
        ELSE v_result := '미정의 카테고리: ' || p_code;
    END CASE;

    RETURN v_result;
END;
$$ LANGUAGE plpgsql;

해결 3: EXCEPTION 블록으로 에러 포착 및 복구

이미 배포된 함수에서 즉각적인 수정이 어려울 때, EXCEPTION 블록으로 case_not_found를 포착하여 서비스를 보호할 수 있습니다.

CREATE OR REPLACE FUNCTION safe_status_handler(p_status TEXT)
RETURNS TEXT AS $$
DECLARE
    v_result TEXT;
BEGIN
    CASE p_status
        WHEN 'ACTIVE'   THEN v_result := '활성';
        WHEN 'INACTIVE' THEN v_result := '비활성';
        WHEN 'BANNED'   THEN v_result := '차단됨';
    END CASE;

    RETURN v_result;

EXCEPTION
    WHEN case_not_found THEN
        -- 에러 정보를 별도 테이블에 기록
        INSERT INTO error_log (func_name, input_value, occurred_at)
        VALUES ('safe_status_handler', p_status, NOW());

        -- 기본값 반환으로 서비스 연속성 유지
        RETURN '알 수 없는 상태';
END;
$$ LANGUAGE plpgsql;

-- error_log 테이블 생성 예시
CREATE TABLE IF NOT EXISTS error_log (
    id          SERIAL PRIMARY KEY,
    func_name   TEXT,
    input_value TEXT,
    occurred_at TIMESTAMPTZ DEFAULT NOW()
);

-- 테스트
SELECT safe_status_handler('DELETED');  -- '알 수 없는 상태' 반환
SELECT * FROM error_log;               -- 에러 기록 확인

예방 방법

1. 코드 리뷰 체크리스트에 ELSE 절 필수 확인 항목 추가

팀 내 코드 리뷰 프로세스에서 PL/pgSQL CASE 구문에 항상 ELSE 절이 포함되어 있는지를 필수 체크 항목으로 지정합니다. 아래 쿼리를 CI/CD 파이프라인이나 정기 점검 스크립트에 포함시켜 ELSE 없는 CASE 구문을 자동으로 감지할 수 있습니다.

-- pg_proc에서 ELSE 없는 CASE 구문이 포함된 함수 탐지 (단순 텍스트 검색)
SELECT
    n.nspname AS schema_name,
    p.proname AS function_name,
    pg_get_functiondef(p.oid) AS function_def
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE p.prolang = (SELECT oid FROM pg_language WHERE lanname = 'plpgsql')
  AND pg_get_functiondef(p.oid) ILIKE '%CASE%'
  AND pg_get_functiondef(p.oid) NOT ILIKE '%ELSE%'
  AND n.nspname NOT IN ('pg_catalog', 'information_schema');

2. 허용 코드 값을 테이블로 관리하고 CHECK 제약 또는 ENUM 타입 활용

상태 코드, 분류 코드 등 CASE 구문에서 다루는 값들을 별도의 코드 테이블이나 PostgreSQL ENUM 타입으로 관리하면, 새로운 값이 추가될 때 함수 코드도 함께 검토해야 한다는 알림 체계를 만들 수 있습니다.

-- ENUM 타입으로 허용 값 제한
CREATE TYPE order_status AS ENUM ('PENDING', 'SHIPPED', 'DELIVERED', 'CANCELLED');

-- ENUM 기반 함수 작성 시 신규 ENUM 값 추가가 곧 함수 수정 신호가 됨
CREATE OR REPLACE FUNCTION get_status_label(p_status order_status)
RETURNS TEXT AS $$
BEGIN
    CASE p_status
        WHEN 'PENDING'   THEN RETURN '대기중';
        WHEN 'SHIPPED'   THEN RETURN '배송중';
        WHEN 'DELIVERED' THEN RETURN '배송완료';
        WHEN 'CANCELLED' THEN RETURN '취소됨';
        ELSE RETURN '정의되지 않음';  -- ENUM 확장 시 안전망
    END CASE;
END;
$$ LANGUAGE plpgsql;

관련 에러

  • P0001 (raise_exception): RAISE EXCEPTION으로 명시적으로 발생시키는 에러로, CASE 구문 내 ELSE 블록에서 의도적으로 에러를 던질 때 함께 활용됩니다.
  • P0004 (assert_failure): ASSERT 구문 실패 시 발생하며, 입력값 검증 로직에서 CASE 구문 대신 ASSERT를 사용할 때 관련될 수 있습니다.
  • 22023 (invalid_parameter_value): 함수 파라미터가 예상 범위를 벗어날 때 발생하며, case not found와 유사한 맥락에서 입력값 문제로 함께 나타나는 경우가 있습니다.
  • 42601 (syntax_error): CASE 구문 자체의 문법 오류로, 런타임 에러인 20000과 달리 컴파일 타임에 발생합니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기