CREATE OR REPLACE FUNCTION valid_comm(p_comm in number) return boolean -- 조건 제어문 함수
IS
v_max_comm number;
BEGIN
SELECT max(commission_pct)
INTO v_max_comm
FROM hr.employees;
IF p_comm > v_max_comm THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END;
/
SELECT * FROM USER_SOURCE WHERE NAME = 'VALID_COMM'; -- PARSE된 코드값 저장
SELECT * FROM USER_OBJECTS WHERE OBJECT_NAME = 'VALID_COMM'; --OBJECT 정보
CREATE OR REPLACE PROCEDURE rest_comm (p_comm number)
IS
v_comm number := 0.1;
BEGIN
IF valid_comm(p_comm) THEN
dbms_output.put_line('old : ' || v_comm);
v_comm := p_comm;
dbms_output.put_line('new : ' || v_comm);
ELSE
RAISE_APPLICATION_ERROR(-20001,'기존 최고값을 넘을 수 없다.');
END IF;
END;
/
INVALID상태가 된다.INVALID한 객체들을 먼저 recompile해야 한다.select * from user_dependencies where referenced_name in ('EMPLOYEES','VALID_COMM');
SELECT * FROM USER_OBJECTS WHERE OBJECT_NAME in ('EMPLOYEES','REST_COMM','VALID_COMM'); --OBJECT 정보

name 객체가 referenced_name 객체를 참조한다.
또는 프로그램을 통해 가능하다.
C:\oraclexe\app\oracle\product\11.2.0\server\rdbms\admin\utldtree.sql 해당 경로의 utldtree 스크립트를 sqlplus에서 실행하면 된다.
실행 방법
테스트를 hr계정에서 하기 때문에 hr계정에 conn한 후 아래 코드를 실행한다.
conn hr/hr
@C:\oraclexe\app\oracle\product\11.2.0\server\rdbms\admin\utldtree.sql
--해당 경로의 sql스크립트 실행
하면 프로시저에 DEPTREE_FILL이라는 프로시저가 생성된다.


해당 프로시저를 실행시키면 deptree_fill view가 생성되고 해당 view를 통해 트리구조로 종속관계를 확인할 수 있다.
desc deptree_fill;
exec deptree_fill('table', 'hr', 'employees');
select * from deptree;

이때 C:\oraclexe\app\oracle\product\11.2.0\serve 까지의 경로를 오라클 홈 이라고 한다.
해당 객체가 변경(modify)해야 할 경우 참조 객체들을 확인하고 해당 참조객체에 영향을 미치는지 파악하고 회의를 진행 후 변경해야 한다.
ALTER TABLE hr.employees MODIFY(commission_pct number(10,3));
-- 다음과 같이 객체의 상태 변경

변경시 해당 객체를 참조하고 있는 객체들이 INVALID가 되면서 LAST_DDL_TIME, TIMESTAMP의 값이 변경 시간으로 최신화 된다.
종속관계에 있는 객체에 대한 마지막 compile TIMESTAMP와 자신의 TIMESTAMP를 비교하여 체크한다.
-> hr.employees.commission_pct 갱신 ~> valid_comm의 invalid (timestamp 갱신)
-> valid_comm의 TIMESTAMP갱신 ~> rest_comm의 invalid (timestamp 갱신)
ALTER FUNCTION valid_comm COMPILE;
ALTER PROCEDURE REST_COMM COMPILE;

컴파일 하더라도 오류나는 경우가 있다. 그때는 소스코드의 문제로 디버깅을 진행해야 한다.
즉, 변수의 타입을 사용할 때 %type을 이용해 참조하는 것이 좋다.
관련성이 있는 서브프로그램(함수, 프로시저), 변수, 타입을 모아놓은 프로그램
즉 모아서 관리하는 프로그램.
패키지의 인터페이스로 외부에서 참조할 수 있는 데이터 유형, 변수, 상수, 예외사항,커서, 서브프로그램을 선언하는 영역이다.
CREATE OR REPLACE PACKAGE comm_pkg
IS
--global 변수
g_comm number := 0.1;
-- PROCEDURE 선언부분만 작성
PROCEDURE rest_comm(comm number);
-- FUNCTION은 RETURN 까지만 선언.
FUNCTION valid_comm(comm number) RETURN BOOLEAN;
-- 즉 IS절 바로 위까지 선언
END; /*선언하는 부분 spec 즉 begin 필요없다.*/
소스코드를 정의하는 영역이다.
CREATE OR REPLACE PACKAGE BODY comm_pkg /* BODY 선언 */
IS
/* CREATE OR REPLACE 제거 */
FUNCTION valid_comm(p_comm in number) return boolean -- 조건 제어문 함수
IS
v_max_comm number;
BEGIN
SELECT max(commission_pct)
INTO v_max_comm
FROM hr.employees;
IF p_comm > v_max_comm THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END valid_comm;
/* CREATE OR REPLACE 제거 */
PROCEDURE rest_comm (p_comm number)
IS
/*v_comm number := 0.1; 글로벌 변수로 대체*/
BEGIN
IF valid_comm(p_comm) THEN
dbms_output.put_line('old : ' || g_comm);
g_comm := p_comm;
dbms_output.put_line('new : ' || g_comm);
ELSE
RAISE_APPLICATION_ERROR(-20001,'기존 최고값을 넘을 수 없다.');
END IF;
END rest_comm;
END; /* BEGIN절은 OPTIONAL한 절이다. */
/
exec comm_pkg.rest_comm(0.2);
반드시 패키지명을 통해서 호출해야 한다.
글로벌 변수는 세션이 죽을 때 까지 살아있다. 다른 세션에서 내 세션의 값을 공유하지 않는다.
글로벌 변수는 보여지지 않고, 사용 가능한 서브 프로그램의 정보를 보여준다.
desc comm_pkg;

해당 패키지의 글로벌 변수를 임의로 조회, 조작할 수 있다. 단, 반드시 패키지명을 함꼐 붙일 것.
exec comm_pkg.g_comm := 0.35
exec comm_pkg.rest_comm(0.3)
exec dbms_output.put_line(comm_pkg.g_comm)
수정시 현재 세션에 대해서는 지속적으로 그 값을 유지하지만, 다른 세션이나 세션이 종료될 경우 초기 값을 사용한다.

PRAGMA SERIALLY_REUSABLE을 글로벌 변수로서 프로시저가 끝나면 초기 값으로 되돌아 오고 싶을 때 사용
SEPC과 BODY에 모두 기술해야 함.
동일한 이름의 서브 프로그램을 생성할 때 사용 기법. - 다형성 (객체지향 프로그램)
동일한 이름의 프로시저, 함수를 만들 수 있다.
형식 파라미터의 개수, 타입 또는 모드가 달라여 된다.
CREATE OR REPLACE PACKAGE pack_over
IS
TYPE t_date_tab IS TABLE OF date INDEX BY pls_integer;
TYPE t_num_tab IS TABLE OF number INDEX BY pls_integer;
PROCEDURE init(tab OUT t_date_tab,n IN number);
PROCEDURE init(tab OUT t_num_tab,n IN number);
END pack_over;
/
CREATE OR REPLACE PACKAGE BODY pack_over
IS
PROCEDURE init(tab OUT t_date_tab,n IN number)
IS
BEGIN
FOR i in 1..n LOOP
tab(i) := sysdate + i;
END LOOP;
END init;
PROCEDURE init(tab OUT t_num_tab,n IN number)
IS
BEGIN
FOR i in 1..n LOOP
tab(i) := i;
END LOOP;
END init;
END pack_over;
/
declare
date_tab pack_over.t_date_tab;
num_tab pack_over.t_num_tab;
begin
pack_over.init(date_tab,5);
pack_over.init(num_tab,5);
for i in 1..5 loop
dbms_output.put_line(date_tab(i));
dbms_output.put_line(num_tab(i));
end loop;
end;
/
임의로 데이터타입을 선언해 사용할 수 없다.
아마 참조에 의한 방식으로 사용하게 하려고 하지 않을까?! (pointer 관점)
배열은 대용량 데이터이기 때문에 데이터를 직접 전달 하는것은 성능상으로 문제가 발생할 수 있다. 즉 패키지에서 생성한 데이터타입을 참조(주소적으로) 하도록 유도하기 위해 오류를 발생시킨다.
호환성 문제이다. user_define_type(collection_type)은 오라클에서 만든 타입이 아니기 때문에 해당 타입에 대해서는 있는 그대로 써야한다.
declare
TYPE t_date_tab IS TABLE OF date INDEX BY pls_integer; -- 사용할 수 없음
date_tab t_date_tab;
num_tab pack_over.t_num_tab;
begin
pack_over.init(date_tab,5);
pack_over.init(num_tab,5);
for i in 1..5 loop
dbms_output.put_line(date_tab(i)); -- 오류 발생
dbms_output.put_line(num_tab(i));
end loop;
end;
/
### 패키지에서 선언한 커서를 open하게 되면 현재 session이 열려있는 동안 계속 open되어 있다. 현재 session을 닫거나 cursor를 close할 때 까지 open 상태를 유지한다.
CREATE OR REPLACE PACKAGE pack_cur
IS
PROCEDURE open;
PROCEDURE next(p_num in number);
PROCEDURE close;
END pack_cur;
/
CREATE OR REPLACE PACKAGE BODY pack_cur
IS
CURSOR c1 IS
SELECT employee_id, last_name
FROM hr.employees
ORDER BY 1 desc;
v_no number; -- protect
v_name varchar2(30); -- protect
PROCEDURE open
IS
BEGIN
if not c1%ISOPEN then
open c1;
dbms_output.put_line('c1 cursor is opend');
end if;
END open;
-- 해당 프로시저에서 프로그램이 종료되어도 계속 열여 있다.
-- 세션이 종료되지 않거나 close 하기 전까지 계속 열려있다.
PROCEDURE next(p_num in number)
IS
BEGIN
LOOP
EXIT WHEN c1%ROWCOUNT >=p_num;
FETCH c1 into v_no, v_name;
dbms_output.put_line(v_no || ' ' || v_name);
END LOOP;
END;
PROCEDURE close
IS
BEGIN
IF c1%ISOPEN THEN
close c1;
dbms_output.put_line('c1 is closed');
END IF;
END close;
END pack_cur;
/
결과 :

주석처리 주의, 단일행 주석보다는 여러행 주석을 사용권장한다.
PRAGMA SERIALLY_REUSABLE
mod의 오버로딩
userdefine타입 package 테스트