1. 程式人生 > >Oracle PL/SQL進階程式設計(第五彈:包的進階技術)

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_objectsuser_source查詢包規範和包體的狀態和程式碼,也可以通過視覺化工具來檢視,如Toad,PL/SQL Developer,Oracle SQL Developer。

使用DROP PACKAGE或DROP PACKAGE BODY來對整個包或包體進行刪除。

檢查包的依賴性

在Oracle中,包頭不依賴於包體,如果對包體進行改變,並不會影響到包頭的狀態,包頭不需要重新編譯。但是如果包頭改變,將會使包體自動失效,因為包體緊密依賴包頭。如果包體中引用的表的表結構改變,包體會失效,但包頭不會失效。

資料字典檢視user_dependenciesall_dependenciesdba_dependencies也可以列出方案物件之間的依賴性關係。