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也可以列出方案對象之間的依賴性關係。

發佈了165 篇原創文章 · 獲贊 47 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章