Oracle PL/SQL進階程式設計(第五彈:包的進階技術)
包過載
包過載實際上就是對包中的子程式的過載,之前我們已經對子程式的過載做過介紹,這裡簡單看下程式碼。
定義包規範:
CREATE OR REPLACE PACKAGE emp_action_pkg_overload IS
--定義一個增加新員工的過程
PROCEDURE newdept (
p_deptno dept.deptno%TYPE, --部門編號
p_dname dept.dname%TYPE, --部門名稱
p_loc dept.loc%TYPE --位置
);
--定義一個增加新員工的過程,過載過程
PROCEDURE newdept (
p_deptno dept.deptno%TYPE, --部門編號
p_dname dept.dname%TYPE --部門名稱
);
--定義一個獲取員工加薪數量的函式
FUNCTION getraisedsalary (p_empno emp.empno%TYPE)
RETURN NUMBER;
--定義一個獲取員工加薪數量的函式,過載函式
FUNCTION getraisedsalary (p_ename emp.ename%TYPE)
RETURN NUMBER;
END emp_action_pkg_overload;
定義包體:
CREATE OR REPLACE PACKAGE BODY emp_action_pkg_overload IS
--公開,實現包規範中定義的newdept過程
PROCEDURE newdept (
p_deptno dept.deptno%TYPE, --部門編號
p_dname dept.dname%TYPE, --部門名稱
p_loc dept.loc%TYPE --位置
)
AS
v_deptcount NUMBER; --儲存是否存在員工編號
BEGIN
SELECT COUNT (*) INTO v_deptcount FROM dept
WHERE deptno = p_deptno; --查詢在dept表中是否存在部門編號
IF v_deptcount > 0 --如果存在相同的員工記錄
THEN --丟擲異常
raise_application_error (-20002, '出現了相同的員工記錄');
END IF;
INSERT INTO dept(deptno, dname, loc)
VALUES (p_deptno, p_dname, p_loc);--插入記錄
END newdept;
PROCEDURE newdept (
p_deptno dept.deptno%TYPE, --部門編號
p_dname dept.dname%TYPE --部門名稱
)
AS
v_deptcount NUMBER; --儲存是否存在員工編號
BEGIN
SELECT COUNT (*) INTO v_deptcount FROM dept
WHERE deptno = p_deptno; --查詢在dept表中是否存在部門編號
IF v_deptcount > 0 --如果存在相同的員工記錄
THEN --丟擲異常
raise_application_error (-20002, '出現了相同的員工記錄');
END IF;
INSERT INTO dept(deptno, dname, loc)
VALUES (p_deptno, p_dname, '中國');--插入記錄
END newdept;
--公開,實現包規範中定義的getraisedsalary函式
FUNCTION getraisedsalary (p_empno emp.empno%TYPE)
RETURN NUMBER
IS
v_job emp.job%TYPE; --職位變數
v_sal emp.sal%TYPE; --薪資變數
v_salaryratio NUMBER (10, 2); --調薪比率
BEGIN
--獲取員工表中的薪資資訊
SELECT job, sal INTO v_job, v_sal FROM emp WHERE empno = p_empno;
CASE v_job --根據不同的職位獲取調薪比率
WHEN '職員' THEN
v_salaryratio := 1.09;
WHEN '銷售人員' THEN
v_salaryratio := 1.11;
WHEN '經理' THEN
v_salaryratio := 1.18;
ELSE
v_salaryratio := 1;
END CASE;
IF v_salaryratio <> 1 --如果有調薪的可能
THEN
RETURN ROUND(v_sal * v_salaryratio,2); --返回調薪後的薪資
ELSE
RETURN v_sal; --否則不返回薪資
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 0; --如果沒找到原工記錄,返回0
END getraisedsalary;
--過載函式的實現
FUNCTION getraisedsalary (p_ename emp.ename%TYPE)
RETURN NUMBER
IS
v_job emp.job%TYPE; --職位變數
v_sal emp.sal%TYPE; --薪資變數
v_salaryratio NUMBER (10, 2); --調薪比率
BEGIN
--獲取員工表中的薪資資訊
SELECT job, sal INTO v_job, v_sal FROM emp WHERE ename = p_ename;
CASE v_job --根據不同的職位獲取調薪比率
WHEN '職員' THEN
v_salaryratio := 1.09;
WHEN '銷售人員' THEN
v_salaryratio := 1.11;
WHEN '經理' THEN
v_salaryratio := 1.18;
ELSE
v_salaryratio := 1;
END CASE;
IF v_salaryratio <> 1 --如果有調薪的可能
THEN
RETURN ROUND(v_sal * v_salaryratio,2); --返回調薪後的薪資
ELSE
RETURN v_sal; --否則不返回薪資
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 0; --如果沒找到原工記錄,返回0
END getraisedsalary;
--私有,該函式在包規範中並不存在,只能在包體內被引用
FUNCTION checkdeptno(p_deptno dept.deptno%TYPE) RETURN NUMBER
AS
v_counter NUMBER(2);
BEGIN
SELECT COUNT(*) INTO v_counter FROM dept WHERE deptno=p_deptno;
RETURN v_counter;
END;
END emp_action_pkg_overload;
呼叫如下:
DECLARE
v_sal NUMBER(10,2);
BEGIN
emp_action_pkg_overload.newdept(43,'樣品部','東京'); --過載過程使用示例
emp_action_pkg_overload.newdept(44,'紙品部');
v_sal:=emp_action_pkg_overload.getraisedsalary(7369); --過載函式使用示例
v_sal:=emp_action_pkg_overload.getraisedsalary('史密斯');
END;
包初始化
當會話第一次使用某個包時,會對包進行初始化,此時會初始化所有包級別的資料,對宣告中的常量或變數指定賦預設值,初始化單元中的程式碼塊。
如果預設的初始化無法滿足要求,例如想執行一些較複雜的初始化工作,那麼需要使用包初始化功能。包初始化單元是位於包體結尾的BEGIN語句和整個包最後的END之間的所有語句。
--定義包頭,在包頭中定義要公開的成員
CREATE OR REPLACE PACKAGE InitTest IS
TYPE emp_typ IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
CURSOR emp_cur RETURN emp%ROWTYPE; --定義遊標
curr_time NUMBER; --當前秒數
emp_tab emp_typ; --定義集合型別的變數
--定義一個增加新員工的過程
PROCEDURE newdept (
p_deptno dept.deptno%TYPE, --部門編號
p_dname dept.dname%TYPE, --部門名稱
p_loc dept.loc%TYPE --位置
);
--定義一個獲取員工加薪數量的函式
FUNCTION getraisedsalary (p_empno emp.empno%TYPE)
RETURN NUMBER;
END InitTest;
--定義包體,在包體的初始化區域對包進行初始化
CREATE OR REPLACE PACKAGE BODY InitTest IS
row_counter NUMBER:=1;
CURSOR emp_cur RETURN emp%ROWTYPE IS
SELECT * FROM emp ORDER BY sal DESC; --定義遊標體
--定義一個增加新員工的過程
PROCEDURE newdept (
p_deptno dept.deptno%TYPE, --部門編號
p_dname dept.dname%TYPE, --部門名稱
p_loc dept.loc%TYPE --位置
) AS
BEGIN
NULL;
END newdept;
--定義一個獲取員工加薪數量的函式
FUNCTION getraisedsalary (p_empno emp.empno%TYPE)
RETURN NUMBER IS
BEGIN
NULL;
END getraisedsalary;
BEGIN
--包初始化部分,定義包的程式碼
SELECT TO_NUMBER(TO_CHAR(SYSDATE,'SSSSS')) INTO curr_time FROM dual;
FOR emp_row IN emp_cur LOOP
emp_tab(row_counter):=emp_row; --為集合賦值
row_counter:=row_counter+1;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('出現了異常');
END InitTest;
呼叫如下:
DECLARE
v_time NUMBER;
BEGIN
v_time:=InitTest.curr_time; --獲取當前的時間秒數
--輸出索引表中的員工名稱,以及當前的秒數。
DBMS_OUTPUT.put_line(InitTest.emp_tab(1).ename||' '||v_time);
END;
包的純度級別
正如子程式可以在SQL語句中直接使用一樣,包中的公共函式也可以在SQL語句中直接使用。同樣,這些公共函式的定義也有一些限制,比如不能包含DML語句,不能讀寫遠端包的變數。這些可以通過包的純度級別來進行限制,定義包純度級別的語法如下:
PRAGMA RESTRICT_REFERENCES (function_name, WNDS[,WNPS][,RNDS][,RNPS]);
- function_name
:指定已經定義的函式名
- WNDS:限制函式不能修改資料庫資料,即禁止函式執行DML操作。
- WNPS:限制函式不能修改包變數,即不能為包變數賦值。
- RNDS:限制函式不能讀取資料庫資料,即不能執行SELECT操作。
- RNPS:限制函式不能讀取包變數,即不能將包變數賦值給其他 變數。
要在包中使用包純度級別,必須首先在包規範中定義函式,然後指定函式的純度級別。
CREATE OR REPLACE PACKAGE purityTest IS
TYPE dept_typ IS TABLE OF dept%ROWTYPE INDEX BY BINARY_INTEGER;
dept_tab dept_typ; --定義集合型別的變數
--定義一個增加新員工的過程
PROCEDURE newdept (
p_deptno dept.deptno%TYPE, --部門編號
p_dname dept.dname%TYPE, --部門名稱
p_loc dept.loc%TYPE --位置
);
--定義一個獲取員工加薪數量的函式
FUNCTION getraisedsalary (p_empno emp.empno%TYPE)
RETURN NUMBER;
--設定純度級別
PRAGMA RESTRICT_REFERENCES(newdept,WNPS);
PRAGMA RESTRICT_REFERENCES(getraisedsalary,WNDS,WNPS,RNPS);
END purityTest;
如果在函式體的定義中越過的純度級別,那麼在編譯時將出現錯誤。
如果要編寫可被SQL語句引用的包的公共函式,函式必須要符合WNDS、WNPS和RNPS這3個純度級別。
包許可權設定
要想讓別的使用者訪問當前會話中建立的包,需要向其他使用者分配EXECUTE的許可權,如:
GRANT EXECUTE ON scott.purityTest TO userb;
檢視和刪除包
可以通過user_objects
,user_source
查詢包規範和包體的狀態和程式碼,也可以通過視覺化工具來檢視,如Toad,PL/SQL Developer,Oracle SQL Developer。
使用DROP PACKAGE或DROP PACKAGE BODY來對整個包或包體進行刪除。
檢查包的依賴性
在Oracle中,包頭不依賴於包體,如果對包體進行改變,並不會影響到包頭的狀態,包頭不需要重新編譯。但是如果包頭改變,將會使包體自動失效,因為包體緊密依賴包頭。如果包體中引用的表的表結構改變,包體會失效,但包頭不會失效。
資料字典檢視user_dependencies
,all_dependencies
,dba_dependencies
也可以列出方案物件之間的依賴性關係。