PL/SQL 변수, 커서, 예외

PL/SQL의 스칼라 변수와 %TYPE/%ROWTYPE, 명시적 커서 OPEN-FETCH-CLOSE 패턴, 커서 FOR 루프, 사용자 정의 예외와 RAISE_APPLICATION_ERROR를 다룹니다.

· 5 min read · PALDYN Team

지난 글에서 PL/SQL 블록의 4개 섹션을 살펴봤다. 이번에는 PL/SQL의 세 핵심 구성 요소인 변수·커서·예외를 깊이 다룬다.

변수와 데이터 타입

스칼라 변수

PL/SQL은 SQL 데이터 타입 외에 BOOLEAN도 지원한다. BOOLEAN은 SQL에서 사용할 수 없고 PL/SQL 내부 로직에서만 쓴다.

DECLARE
  v_employee_id  NUMBER(6)     := 100;
  v_last_name    VARCHAR2(25);
  v_flag         BOOLEAN       := TRUE;
  v_hire_date    DATE          := SYSDATE;
  c_tax_rate     CONSTANT NUMBER := 0.033;
BEGIN
  IF v_flag THEN
    DBMS_OUTPUT.PUT_LINE('플래그 활성');
  END IF;
END;
/

%TYPE와 %ROWTYPE

DECLARE
  -- %TYPE: 특정 컬럼과 동일한 타입으로 선언
  v_salary    employees.salary%TYPE;
  v_name      employees.last_name%TYPE;

  -- %ROWTYPE: 테이블 또는 커서 행 전체를 레코드로
  r_emp       employees%ROWTYPE;
BEGIN
  SELECT * INTO r_emp
  FROM   employees
  WHERE  employee_id = 100;

  DBMS_OUTPUT.PUT_LINE(r_emp.last_name || ': ' || r_emp.salary);
END;
/

테이블 컬럼 타입이 변경될 때 %TYPE을 사용한 코드는 재컴파일만으로 자동 반영된다. %ROWTYPE은 SELECT *의 결과를 변수 하나로 받을 때 유용하다.

PL/SQL 변수, 커서, 예외 개념


묵시적 커서 vs. 명시적 커서

구분묵시적 커서명시적 커서
선언자동개발자 직접 선언
사용DML / SELECT INTO다중 행 결과 반복
속성SQL%ROWCOUNT, SQL%FOUNDcur%ROWCOUNT, cur%NOTFOUND
OPEN/CLOSE자동수동 (또는 커서 FOR 루프)

단일 행 결과가 확실할 때는 SELECT INTO(묵시적 커서)를, 다중 행을 순회해야 할 때는 명시적 커서를 사용한다.

명시적 커서 OPEN-FETCH-CLOSE

DECLARE
  CURSOR c_high_sal (p_min NUMBER) IS
    SELECT employee_id, last_name, salary
    FROM   employees
    WHERE  salary >= p_min
    ORDER  BY salary DESC;

  TYPE t_emp_rec IS RECORD (
    emp_id   employees.employee_id%TYPE,
    nm       employees.last_name%TYPE,
    sal      employees.salary%TYPE
  );
  r t_emp_rec;
BEGIN
  OPEN c_high_sal(10000);
  LOOP
    FETCH c_high_sal INTO r;
    EXIT WHEN c_high_sal%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(r.nm || ' / ' || r.sal);
  END LOOP;
  CLOSE c_high_sal;
END;
/

%NOTFOUND를 FETCH 이후에 확인해야 마지막 행을 놓치지 않는다.

커서 FOR 루프 (권장)

커서 FOR 루프는 OPEN/FETCH/CLOSE를 내부적으로 자동 처리한다.

BEGIN
  FOR r IN (
    SELECT employee_id, last_name
    FROM   employees
    WHERE  department_id = 60
  ) LOOP
    DBMS_OUTPUT.PUT_LINE(r.last_name);
  END LOOP;
END;
/

커서 변수 선언도 필요 없고, CLOSE 누락 버그도 없다. 대부분의 경우 커서 FOR 루프가 가장 간결하고 안전하다.

명시적 커서와 사용자 정의 예외


예외 처리

사전 정의 예외

Oracle이 미리 이름을 붙여 둔 예외들이다.

예외명원인
NO_DATA_FOUNDSELECT INTO 결과가 0행
TOO_MANY_ROWSSELECT INTO 결과가 2행 이상
DUP_VAL_ON_INDEXUnique 제약 위반
VALUE_ERROR타입 변환 또는 길이 초과
ZERO_DIVIDE0으로 나누기
CURSOR_ALREADY_OPEN이미 열린 커서를 다시 열기

사용자 정의 예외

비즈니스 규칙 위반을 표현할 때 사용한다.

DECLARE
  e_low_stock EXCEPTION;
  v_qty       NUMBER := 5;
BEGIN
  IF v_qty < 10 THEN
    RAISE e_low_stock;
  END IF;
EXCEPTION
  WHEN e_low_stock THEN
    DBMS_OUTPUT.PUT_LINE('재고 부족 경고');
END;
/

PRAGMA EXCEPTION_INIT

ORA- 번호를 예외 이름과 연결한다.

DECLARE
  e_parent_key_not_found EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_parent_key_not_found, -2291);  -- FK 위반
BEGIN
  INSERT INTO order_items VALUES (9999, 999, 1);
EXCEPTION
  WHEN e_parent_key_not_found THEN
    DBMS_OUTPUT.PUT_LINE('존재하지 않는 주문 번호');
END;
/

RAISE_APPLICATION_ERROR

애플리케이션 계층으로 오류를 반환할 때 사용한다. 번호는 -20000 ~ -20999 범위 내에서 자유롭게 지정한다.

CREATE OR REPLACE PROCEDURE set_salary (
  p_emp_id IN employees.employee_id%TYPE,
  p_sal    IN employees.salary%TYPE
) AS
BEGIN
  IF p_sal < 0 THEN
    RAISE_APPLICATION_ERROR(-20001, '급여는 음수일 수 없습니다.');
  END IF;
  UPDATE employees SET salary = p_sal WHERE employee_id = p_emp_id;
END;
/

호출자(Java, Python 등)는 이 번호와 메시지를 SQL 예외로 수신한다. 모든 예외를 WHEN OTHERS로 삼키지 말고, 가능하면 구체적인 예외를 처리하고 RAISE 또는 RAISE_APPLICATION_ERROR로 재전파해야 디버깅이 쉬워진다.


지난 글: PL/SQL 블록 구조

다음 글: PL/SQL 패키지


읽어주셔서 감사합니다. 😊