패키지 데이터
패키지에는 서브 프로그램뿐만 아니라 상수, 변수, 커서, 레코드, 컬렉션, 예외까지 선언해서 사용할 수 있다.
굳이 상수나 변수, 커서 등을 패키지에서 선언해 사용할 필요가 있을까?
상수와 변수 선언
패키지 안에 상수나 변수를 선언하게 되면 이들의 생존주기는 세션 단위로, 한 세션이 살아있는 동안에는 그 값이 메모리 상에 유지된다.
즉, 한 번 로그인한 뒤 로그아웃을 하기 전까지는 그 값이 공유된다. 변수의 경우, 해당 패키지의 사용이 끝났더라도 같은 세션에서는 그 값을 공유할 수 있다.
| 1 2 3 4 | CREATE OR REPLACE PACKAGE CH12_VAR IS     C_TEST  CONSTANT    VARCHAR2(10)    := 'TEST';     V_TEST  VARCHAR2(10); END; | cs | 
| 1 2 3 4 | BEGIN     DBMS_OUTPUT.PUT_LINE('상수 : ' || CH12_VAR.C_TEST);     DBMS_OUTPUT.PUT_LINE('변수 : ' || CH12_VAR.V_TEST); END; | cs | 
| 1 2 3 4 5 6 7 | BEGIN     DBMS_OUTPUT.PUT_LINE('값 설정 전 : ' || CH12_VAR.V_TEST);     CH12_VAR.V_TEST := 'FIRST';     DBMS_OUTPUT.PUT_LINE('값 설정 후 : ' || CH12_VAR.V_TEST); END; | cs | 
패키지 선언부에 선언한 변수는 외부에서 접근하고 수정할 수 있는데, 이를 공유 항목이라고도 한다.
위 익명 블록에서 변수의 값을 설정을 했으면, 익명 블록이 종료되더라도 세션이 살아있다면 변수의 값은 다시 조회해도 동일할 것이다. 하지만 세션을 종료하면 원래의 값으로 돌아간다.
| 1 2 3 | BEGIN     DBMS_OUTPUT.PUT_LINE('변수 : ' || CH12_VAR.V_TEST); END; | cs | 
패키지 선언부가 아닌 본문에서 상수나 변수를 선언해 값을 할당할 수 있는데, 이 경우 외부에서 이 상수나 변수를 참조할 수 없다. 이를 내부(전용)항목이라고 한다.
하지만, 직접 참조는 불가능하나 서브 프로그램을 통해 간접적으로 참조하거나 값을 변경할 수 있다.
| 1 2 3 4 | CREATE OR REPLACE PACKAGE BODY CH12_VAR IS     C_TEST_BODY CONSTANT    VARCHAR2(10)    := 'TEST';     V_TEST_BODY VARCHAR2(10); END CH12_VAR; | cs | 
직접 참조를 시도해보자.
| 1 2 3 4 | BEGIN     DBMS_OUTPUT.PUT_LINE('상수 : ' || CH12_VAR.C_TEST_BODY);     DBMS_OUTPUT.PUT_LINE('변수 : ' || CH12_VAR.V_TEST_BODY); END; | cs | 
오류가 발생하는 것을 확인할 수 있다. 그렇다면, 이번에는 내부변수와 상수를 처리하는 함수와 프로시저 추가하고 실행해보자.
| 1 2 3 4 5 6 7 | CREATE OR REPLACE PACKAGE CH12_VAR IS     C_TEST_BODY CONSTANT    VARCHAR2(10)    := 'TEST';     V_TEST_BODY VARCHAR2(10);     FUNCTION    FN_GET_VALUE    RETURN  VARCHAR2;     PROCEDURE   SP_SET_VALUE (PS_VALUE  VARCHAR2); END CH12_VAR; | cs | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | CREATE OR REPLACE PACKAGE BODY CH12_VAR IS     C_TEST_BODY CONSTANT    VARCHAR2(10)    := 'TEST';     V_TEST_BODY VARCHAR2(10);     FUNCTION    FN_GET_VALUE    RETURN  VARCHAR2     IS     BEGIN         RETURN NVL(V_TEST_BODY, 'NULL입니다');     END FN_GET_VALUE;     PROCEDURE   SP_SET_VALUE (PS_VALUE  VARCHAR2)     IS     BEGIN         V_TEST_BODY := PS_VALUE;     END SP_SET_VALUE; END CH12_VAR; | cs | 
| 1 2 3 4 5 6 7 8 | DECLARE     VS_VALUE    VARCHAR2(10); BEGIN     CH12_VAR.SP_SET_VALUE('EXTERNAL');     VS_VALUE := CH12_VAR.FN_GET_VALUE;     DBMS_OUTPUT.PUT_LINE(VS_VALUE); END; | cs | 
위에서 공유 항목은 세션이 유지되어있으면 값이 유지된다고 했다. 세션을 유지한 채로 아래 쿼리를 실행해보자. 세션 유지라고해서 별거 없다. 접속 종료만 하지 않으면 된다.
| 1 2 3 | BEGIN     DBMS_OUTPUT.PUT_LINE(CH12_VAR.FN_GET_VALUE); END; | cs | 
커서
명시적 커서를 패키지에서 선언하고 사용할 수 있는데, 변수나 상수와 같이 패키지 내에서 선언한 커서는 세션이 살아있는 동안 유지된다.
변수는 값이 유지되지만, 커서는 그 상태까지 유지된다. 패키지에서는 두가지 형태로 명시적 커서를 선언할 수 있다.
패키지 선언부에서 커서 전체를 선언하는 형태
| 1 2 3 4 5 6 7 8 | CREATE OR REPLACE PACKAGE CH12_CUR_PKG IS     CURSOR  PC_EMPDEP_CUR (DEP_ID IN DEPARTMENTS.DEPARTMENT_ID%TYPE)     IS         SELECT  A.EMPLOYEE_ID, A.EMP_NAME, B.DEPARTMENT_NAME         FROM    EMPLOYEES A, DEPARTMENTS B         WHERE   A.DEPARTMENT_ID = DEP_ID         AND     A.DEPARTMENT_ID = B.DEPARTMENT_ID; END CH12_CUR_PKG; | cs | 
| 1 2 3 4 5 6 | BEGIN     FOR REC IN CH12_CUR_PKG.PC_EMPDEP_CUR(30)     LOOP         DBMS_OUTPUT.PUT_LINE(REC.EMP_NAME || ' - ' || REC.DEPARTMENT_NAME);     END LOOP; END; | cs | 
쿼리를 제외한 커서 헤더부분만 선언하는 형태
커서 헤더만 선언부에 명시하면 해당 커서의 쿼리는 패키지 본문에서 작성해야한다. 따라서, 커서 구현부 쿼리는 외부로부터 숨길 수 있다.
또한, 헤더 부분만 선언할 때는 커서가 반환,패치하는 데이터를 가리키는 RETURN절을 명시해야한다.
즉 구현부의 쿼리 결과로 반환되는 컬럼의 타입을 RETURN절과 함께 명시해야한다는 의미이다.
| 1 2 3 4 | CREATE OR REPLACE PACKAGE CH12_CUR_PKG IS     CURSOR  PC_DEPNAME_CUR (DEP_ID IN DEPARTMENTS.DEPARTMENT_ID%TYPE)             RETURN  DEPARTMENTS%ROWTYPE; END CH12_CUR_PKG; | cs | 
| 1 2 3 4 5 6 7 8 | CREATE OR REPLACE PACKAGE BODY CH12_CUR_PKG IS     CURSOR  PC_DEPNAME_CUR (DEP_ID IN DEPARTMENTS.DEPARTMENT_ID%TYPE)         RETURN  DEPARTMENTS%ROWTYPE     IS         SELECT  *         FROM    DEPARTMENTS         WHERE   DEPARTMENT_ID = DEP_ID; END CH12_CUR_PKG; | cs | 
| 1 2 3 4 5 6 | BEGIN     FOR REC IN CH12_CUR_PKG.PC_DEPNAME_CUR(30)     LOOP         DBMS_OUTPUT.PUT_LINE(REC.DEPARTMENT_ID || ' - ' || REC.DEPARTMENT_NAME);     END LOOP; END; | cs | 
| 1 2 3 4 5 6 7 8 9 | CREATE OR REPLACE PACKAGE CH12_CUR_PKG IS     TYPE    EMP_DEP_RT  IS RECORD      (   EMP_ID      EMPLOYEES.EMPLOYEE_ID%TYPE,         EMP_NAME    EMPLOYEES.EMP_NAME%TYPE,         JOB_TITLE   JOBS.JOB_TITLE%TYPE    );     CURSOR  PC_EMPDEP2_CUR  (P_JOB_ID IN JOBS.JOB_ID%TYPE)             RETURN EMP_DEP_RT; END; | cs | 
| 1 2 3 4 5 6 7 8 9 | CREATE OR REPLACE PACKAGE BODY CH12_CUR_PKG IS     CURSOR  PC_EMPDEP2_CUR  (P_JOB_ID IN JOBS.JOB_ID%TYPE)         RETURN EMP_DEP_RT     IS         SELECT  A.EMPLOYEE_ID, A.EMP_NAME, B.JOB_TITLE         FROM    EMPLOYEES A, JOBS B         WHERE   A.JOB_ID = P_JOB_ID         AND     A.JOB_ID = B.JOB_ID; END; | cs | 
| 1 2 3 4 5 6 | BEGIN     FOR REC IN CH12_CUR_PKG.PC_EMPDEP2_CUR('FI_ACCOUNT')     LOOP            DBMS_OUTPUT.PUT_LINE(REC.EMP_ID || ' - ' || REC.EMP_NAME || ' - ' || REC.JOB_TITLE);     END LOOP; END; | cs | 
패키지 커서 사용시 주의점
LOOP나 WHILE문을 사용할 때는 '커서열기-패치-닫기' 과정을 직접 명시해줘야한다.
패키지 커서는 커서를 닫는 것을 누락하면 같은 세션에서 해당 커서를 재사용할 때 문제가 발생한다.
| 1 2 3 4 5 6 7 8 9 10 11 | DECLARE     DEP_CUR     CH12_CUR_PKG.PC_EMPDEP2_CUR%ROWTYPE; BEGIN     OPEN    CH12_CUR_PKG.PC_EMPDEP2_CUR('FI_ACCOUNT');     LOOP         FETCH   CH12_CUR_PKG.PC_EMPDEP2_CUR INTO DEP_CUR;         EXIT    WHEN CH12_CUR_PKG.PC_EMPDEP2_CUR%NOTFOUND;         DBMS_OUTPUT.PUT_LINE(DEP_CUR.EMP_ID || ' - ' || DEP_CUR.EMP_NAME || ' - ' || DEP_CUR.JOB_TITLE);     END LOOP; END; | cs | 
위 쿼리를 한 번 더 실행해보자.
커서를 닫지 않았기 때문에 오류가 발생한다.
레코드와 컬렉션
| 1 2 3 4 5 6 7 | CREATE OR REPLACE PACKAGE CH12_COL_PKG IS     TYPE NT_DEP_NAME IS TABLE OF VARCHAR2(30);     PV_NT_DEP_NAME  NT_DEP_NAME := NT_DEP_NAME();     PROCEDURE MAKE_DEP_PROC (P_PAR_ID IN NUMBER); END CH12_COL_PKG; | cs | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | CREATE OR REPLACE PACKAGE BODY CH12_COL_PKG IS     PROCEDURE MAKE_DEP_PROC (P_PAR_ID IN NUMBER)     IS     BEGIN         FOR REC IN (SELECT  DEPARTMENT_NAME                     FROM    DEPARTMENTS                     WHERE   PARENT_ID = P_PAR_ID)         LOOP             PV_NT_DEP_NAME.EXTEND();             PV_NT_DEP_NAME(PV_NT_DEP_NAME.COUNT) := REC.DEPARTMENT_NAME;         END LOOP;     END MAKE_DEP_PROC; END CH12_COL_PKG; | cs | 
| 1 2 3 4 5 6 7 8 | BEGIN     CH12_COL_PKG.MAKE_DEP_PROC(100);     FOR IDX IN 1..CH12_COL_PKG.PV_NT_DEP_NAME.COUNT     LOOP         DBMS_OUTPUT.PUT_LINE(CH12_COL_PKG.PV_NT_DEP_NAME(IDX));     END LOOP; END; | cs | 
같은 값이 공유되고 있음을 아래 쿼리로 확인해보자.
| 1 2 3 4 5 6 | BEGIN     FOR IDX IN 1..CH12_COL_PKG.PV_NT_DEP_NAME.COUNT     LOOP         DBMS_OUTPUT.PUT_LINE(CH12_COL_PKG.PV_NT_DEP_NAME(IDX));     END LOOP; END; | cs | 
'📃 DATABASE > ORACLE' 카테고리의 다른 글
| [PL/SQL] 동적 SQL (0) | 2018.03.01 | 
|---|---|
| [PL/SQL] 기타 패키지 특징 (0) | 2018.02.22 | 
| [PL/SQL] 패키지(PACKAGE) (1) | 2018.02.19 | 
| [PL/SQL] 사용자 정의 타입 (2) | 2018.02.12 | 
| [PL/SQL] 컬렉션 메서드 (0) | 2018.02.12 |