SELECT INTO는 단 한 건의 결과만 처리할 수 있다는 것을 배웠습니다. 하지만 실제 업무에서는 특정 조건을 만족하는 여러 건의 데이터를 하나씩 순회하며 복잡한 로직을 처리해야 할 때가 훨씬 많습니다. 이때 사용하는 것이 바로 **커서(Cursor)**입니다.
커서는 특정 SQL 문장의 결과 집합(Result Set)을 가리키는 **’포인터’ 또는 ‘책갈피’**와 같습니다. 커서를 사용하면, 여러 건의 조회 결과를 한 번에 메모리에 올리지 않고 한 행씩(Row-by-Row) 접근하여 안정적으로 처리할 수 있습니다.
## 가장 간편한 커서: 암시적 커서 (Implicit Cursor)
Oracle은 개발자의 편의를 위해 커서를 사용하는 가장 쉬운 방법인 FOR ... IN (SELECT ...) 루프를 제공합니다. 이를 ‘암시적 커서’라고 부르며, 개발자가 커서를 직접 선언(DECLARE), 열고(OPEN), 값을 가져오고(FETCH), 닫는(CLOSE) 복잡한 과정을 신경 쓸 필요 없이 Oracle이 내부적으로 모두 처리해 줍니다.
- 기본 구조:
record_name: 루프가 돌 때마다 현재 행의 데이터를 담는 임시 레코드 변수입니다. 이 변수는 따로 선언할 필요 없이FOR루프에 의해 자동으로 선언됩니다.SELECT_statement: 처리하고자 하는 데이터들을 조회하는SELECT문입니다.LOOP ... END LOOP;: 조회된 데이터가 없을 때까지 자동으로 한 행씩 반복 실행됩니다. 루프가 끝나면 커서는 자동으로 닫힙니다.
BEGIN
FOR record_name IN (SELECT_statement) LOOP
-- record_name을 통해 현재 행의 각 컬럼에 접근
-- 예: record_name.column_name
-- 현재 행에 대한 처리 로직
END LOOP;
END;
## 사용 예시: 특정 부서의 모든 사원 정보 출력
10번 부서에 소속된 모든 사원의 이름과 급여를 조회하여 출력하는 프로시저를 암시적 커서를 사용하여 만들어 보겠습니다.
CREATE OR REPLACE PROCEDURE PRC_PRINT_DEPT_EMPS
(p_deptno IN EMP.DEPTNO%TYPE)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE(p_deptno || '번 부서 사원 목록');
DBMS_OUTPUT.PUT_LINE('--------------------');
-- p_deptno 부서의 모든 사원을 한 명씩 emp_rec에 담아 반복 처리
FOR emp_rec IN (SELECT ENAME, SAL FROM EMP WHERE DEPTNO = p_deptno) LOOP
-- emp_rec 레코드 변수를 통해 현재 사원의 ENAME과 SAL 컬럼에 접근
DBMS_OUTPUT.PUT_LINE('사원명: ' || emp_rec.ename || ', 급여: ' || emp_rec.sal);
END LOOP;
DBMS_OUTPUT.PUT_LINE('--------------------');
END;
/
-- 실행
EXEC PRC_PRINT_DEPT_EMPS(10);
- 실행 결과:
10번 부서 사원 목록 -------------------- 사원명: CLARK, 급여: 2450 사원명: KING, 급여: 5000 사원명: MILLER, 급여: 1300 --------------------
FOR루프가DEPTNO = 10인 3명의 사원 데이터를 자동으로 순회하며, 루프 안의DBMS_OUTPUT.PUT_LINE을 세 번 실행한 것을 볼 수 있습니다.
## 명시적 커서 (Explicit Cursor)란?
FOR 루프 외에, 개발자가 커서의 모든 생명주기(DECLARE -> OPEN -> FETCH -> CLOSE)를 직접 제어하는 방법을 ‘명시적 커서’라고 합니다. 복잡한 조건에 따라 커서 처리를 중단하거나 여러 커서를 동시에 제어해야 하는 고급 시나리오에서 사용됩니다.
초보자에게는 명시적 커서보다 암시적 커서(
FOR루프)의 사용을 강력히 권장합니다. 코드가 훨씬 간결하고, 커서를 닫는 것을 잊어버리는 등의 실수를 방지하여 더 안전하기 때문입니다.
## 명시적 커서의 4단계 생명주기
1. DECLARE (선언)
- 역할:
DECLARE선언부에 사용할 커서의 이름을 정하고, 해당 커서가 어떤SELECT문을 가리킬지 정의합니다. 아직 쿼리가 실행되지는 않습니다. - 구문:
CURSOR cursor_name IS SELECT_statement;
2. OPEN (열기)
- 역할:
BEGIN실행부에서OPEN명령어로 커서를 엽니다. 이때SELECT문이 실제로 실행되고, 결과 집합(Result Set)이 메모리에 준비되며 커서는 첫 번째 행을 가리키기 직전 위치에 놓입니다. - 구문:
OPEN cursor_name;
3. FETCH (가져오기)
- 역할: 커서가 가리키는 현재 행의 데이터를 변수로 가져옵니다.
FETCH가 실행될 때마다 커서는 다음 행으로 이동합니다. 더 이상 가져올 데이터가 없을 때까지LOOP문 안에서 반복적으로 수행됩니다. - 구문:
FETCH cursor_name INTO variable1, variable2, ...;
4. CLOSE (닫기)
- 역할: 데이터 처리가 모두 끝나면, 커서가 사용하던 모든 리소스를 해제하기 위해 반드시 닫아주어야 합니다. 커서를 닫지 않으면 불필요한 메모리 누수가 발생할 수 있습니다.
- 구문:
CLOSE cursor_name;
## 사용 예시: 명시적 커서로 사원 정보 출력
암시적 커서 예제와 동일하게, 10번 부서의 모든 사원 정보를 출력하는 프로시저를 이번에는 명시적 커서로 작성해 보겠습니다.
SQL
CREATE OR REPLACE PROCEDURE PRC_PRINT_DEPT_EMPS_EXPLICIT
(p_deptno IN EMP.DEPTNO%TYPE)
IS
-- 1. DECLARE: 커서 선언
CURSOR c_emp IS
SELECT ENAME, SAL FROM EMP WHERE DEPTNO = p_deptno;
-- FETCH로 가져온 데이터를 담을 변수 선언
v_ename EMP.ENAME%TYPE;
v_sal EMP.SAL%TYPE;
BEGIN
-- 2. OPEN: 커서 열기
OPEN c_emp;
DBMS_OUTPUT.PUT_LINE(p_deptno || '번 부서 사원 목록');
DBMS_OUTPUT.PUT_LINE('--------------------');
LOOP
-- 3. FETCH: 데이터 가져오기
FETCH c_emp INTO v_ename, v_sal;
-- EXIT WHEN: 더 이상 가져올 데이터가 없으면 루프 탈출
EXIT WHEN c_emp%NOTFOUND;
-- 현재 행에 대한 처리 로직
DBMS_OUTPUT.PUT_LINE('사원명: ' || v_ename || ', 급여: ' || v_sal);
END LOOP;
-- 4. CLOSE: 커서 닫기
CLOSE c_emp;
DBMS_OUTPUT.PUT_LINE('--------------------');
END;
/
-- 실행
EXEC PRC_PRINT_DEPT_EMPS_EXPLICIT(10);
## 커서 속성: %FOUND 와 %NOTFOUND
FETCH 이후에 커서의 상태를 확인하는 것은 매우 중요합니다.
cursor_name%NOTFOUND: 마지막FETCH가 데이터를 가져오는 데 실패했을 때TRUE를 반환합니다.LOOP를 빠져나가는 조건으로 가장 흔하게 사용됩니다.cursor_name%FOUND: 마지막FETCH가 성공적으로 데이터를 가져왔을 때TRUE를 반환합니다.c_emp%NOTFOUND와 정반대입니다.
## 명시적 커서는 언제 사용해야 할까?
- 복잡한 종료 조건: 단순한 루프가 아니라, 특정 조건을 만족했을 때 중간에 처리를 중단해야 할 경우 유용합니다.
- 여러 커서 동시 사용: 하나의 루프 안에서 여러 커서를 번갈아 열고 닫으며 복잡한 데이터 관계를 처리해야 할 때 사용합니다.
- 동적 SQL과의 결합:
OPEN FOR구문을 사용하여, 런타임에 만들어진 동적SELECT문의 결과 집합을 커서로 처리할 수 있습니다.
결론적으로, 대부분의 일상적인 다중 행 처리 작업은 **암시적 커서(FOR ... IN)**로 충분하며 훨씬 간결하고 안전합니다. 명시적 커서는 위와 같이 꼭 필요한 고급 제어 시나리오에서 그 진가를 발휘하는, 강력하지만 신중하게 사용해야 하는 도구입니다.