troubleshooting2025-02-23·9 min·176/348

PostgreSQL 함수(function) 생성 에러: PL/pgSQL 디버깅 가이드

PostgreSQL 사용자 정의 함수 생성 시 발생하는 에러와 PL/pgSQL 디버깅 방법을 설명합니다.

PostgreSQL 함수(function) 생성 에러

Introduction

PostgreSQL의 사용자 정의 함수는 복잡한 비즈니스 로직을 데이터베이스 레벨에서 처리할 수 있게 해주는 강력한 기능입니다. 하지만 PL/pgSQL 함수를 작성하면서 다양한 구문 에러와 런타임 에러에 직면할 수 있습니다. 이 글에서는 흔한 함수 생성 에러와 해결 방법을 다루겠습니다.

Environment

-- PostgreSQL 버전 확인
SELECT version();

-- PL/pgSQL 언어 확인
SELECT * FROM pg_language WHERE lanname = 'plpgsql';
# psql 연결
psql -U postgres -d mydb
-- 함수 생성 테스트
CREATE OR REPLACE FUNCTION calculate_bonus(
    p_employee_id INTEGER,
    p_base_salary NUMERIC
)
RETURNS NUMERIC AS $$
BEGIN
    RETURN p_base_salary * 1.1;
END;
$$ LANGUAGE plpgsql;

Problem

함수 생성 및 실행 시 발생하는 에러들:

-- 에러 1: 반환 타입 불일치
CREATE FUNCTION get_user_name(p_id INTEGER)
RETURNS VARCHAR AS $$
BEGIN
    RETURN 123;  -- 정수 반환 시도
END;
$$ LANGUAGE plpgsql;
-- ERROR: RETURN types don't match

-- 에러 2: 변수 선언 오류
CREATE FUNCTION process_data() RETURNS VOID AS $$
DECLARE
    v_count INTEGER;
    v_name TEXT
BEGIN  -- 세미콜론 누락
    v_count := 0;
END;
$$ LANGUAGE plpgsql;
-- ERROR: syntax error at or near "BEGIN"

-- 에러 3: NULL 처리 에러
CREATE FUNCTION divide_numbers(a INTEGER, b INTEGER)
RETURNS NUMERIC AS $$
BEGIN
    RETURN a / b;  -- b가 0이거나 NULL인 경우
END;
$$ LANGUAGE plpgsql;
-- Division by zero 또는 NULL 반환
# 함수 컴파일 에러 확인
psql -U postgres -d mydb -c "
CREATE FUNCTION test_func() RETURNS VOID AS \$\$
BEGIN
    RAISE NOTICE 'Hello';
END;
\$\$ LANGUAGE plpgsql;
"
# NOTICE: function test_func() doesn't exist yet
# DETAIL: There is no built-in function named "test_func".

Analysis

함수 에러의 원인을 분석했습니다:

-- 함수 존재 여부 확인
SELECT 
    routine_name,
    routine_type,
    data_type
FROM information_schema.routines
WHERE routine_schema = 'public';

-- 함수 의존성 확인
SELECT 
    dep_class.relname as dependent_object,
    dep.objid::regproc as function_name
FROM pg_depend dep
JOIN pg_class dep_class ON dep.depid = dep_class.oid
WHERE dep_class.relkind = 'f';

-- 함수 에러 로그 확인
SHOW log_destination;
SHOW logging_collector;
-- 함수 파싱 에러 디버깅
DO $$
DECLARE
    v_test INTEGER;
BEGIN
    v_test := 'abc'::INTEGER;  -- 타입 캐스팅 에러
EXCEPTION WHEN others THEN
    RAISE NOTICE 'Error: %', SQLERRM;
END;
$$;

Solution

1단계: 올바른 함수 구문 작성

-- 안전한 함수 작성 패턴
CREATE OR REPLACE FUNCTION safe_divide(
    p_numerator NUMERIC,
    p_denominator NUMERIC
)
RETURNS NUMERIC AS $$
BEGIN
    -- NULL 체크
    IF p_numerator IS NULL OR p_denominator IS NULL THEN
        RETURN NULL;
    END IF;
    
    -- 0으로 나누기 방지
    IF p_denominator = 0 THEN
        RAISE EXCEPTION 'Division by zero is not allowed';
    END IF;
    
    RETURN p_numerator / p_denominator;
END;
$$ LANGUAGE plpgsql;

-- 테스트
SELECT safe_divide(10, 3);   -- 3.3333333333333333
SELECT safe_divide(10, 0);   -- ERROR: Division by zero
SELECT safe_divide(10, NULL); -- NULL

2단계: 예외 처리 구현

-- 종합적인 예외 처리 함수
CREATE OR REPLACE FUNCTION process_order(p_order_id INTEGER)
RETURNS JSONB AS $$
DECLARE
    v_order RECORD;
    v_result JSONB;
BEGIN
    -- 주문 조회
    SELECT * INTO v_order
    FROM orders
    WHERE id = p_order_id;
    
    -- 주문 없음 처리
    IF NOT FOUND THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', 'Order not found',
            'order_id', p_order_id
        );
    END IF;
    
    -- 주문 처리 로직
    UPDATE orders
    SET status = 'processed',
        processed_at = CURRENT_TIMESTAMP
    WHERE id = p_order_id;
    
    v_result := jsonb_build_object(
        'success', true,
        'order_id', p_order_id,
        'message', 'Order processed successfully'
    );
    
    RETURN v_result;
    
EXCEPTION
    WHEN OTHERS THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', SQLERRM,
            'sqlstate', SQLSTATE
        );
END;
$$ LANGUAGE plpgsql;

3단계: 디버깅 기법

-- RAISE NOTICE를 활용한 디버깅
CREATE OR REPLACE FUNCTION debug_example(p_input INTEGER)
RETURNS INTEGER AS $$
DECLARE
    v_temp INTEGER;
BEGIN
    RAISE NOTICE 'Input value: %', p_input;
    
    v_temp := p_input * 2;
    RAISE NOTICE 'After multiplication: %', v_temp;
    
    IF v_temp > 100 THEN
        RAISE NOTICE 'Value exceeds threshold';
        RETURN v_temp;
    ELSE
        RAISE NOTICE 'Value is within normal range';
        RETURN v_temp;
    END IF;
END;
$$ LANGUAGE plpgsql;

-- 실행 및 디버그 출력 확인
SELECT debug_example(50);
-- NOTICE:  Input value: 50
-- NOTICE:  After multiplication: 100
-- NOTICE:  Value exceeds threshold
-- 로깅을 활용한 디버깅
CREATE OR REPLACE FUNCTION log_example(p_data JSONB)
RETURNS VOID AS $$
BEGIN
    -- 함수 시작 로그
    INSERT INTO function_logs (function_name, input_data, log_time)
    VALUES ('log_example', p_data, CURRENT_TIMESTAMP);
    
    -- 비즈니스 로직 처리
    -- ...
    
    -- 함수 종료 로그
    INSERT INTO function_logs (function_name, input_data, log_time, status)
    VALUES ('log_example', p_data, CURRENT_TIMESTAMP, 'completed');
END;
$$ LANGUAGE plpgsql;

Lessons Learned

  1. 반환 타입 명시: 함수의 반환 타입을 명확하게 정의하고, 모든 경로에서 일관된 타입을 반환해야 합니다
  2. NULL 처리 필수: 모든 입력 매개변수에 대한 NULL 체크를 수행해야 합니다
  3. 예외 처리 구현: EXCEPTION 블록으로 런타임 에러를 처리하고 유용한 에러 메시지를 제공해야 합니다
  4. RAISE NOTICE 활용: 디버깅 시 RAISE NOTICE로 변수 값을 출력하면 문제 해결에 도움이 됩니다
  5. 함수 주석 작성: 함수의 목적, 매개변수, 반환값을 주석으로 문서화해야 합니다

이 블로그는 외부 스폰서십, 제휴 마케팅 또는 광고 수익을 받지 않습니다.