Oracle PL/SQL進階編程(第三彈:子程序進階技術)

在SQL中調用子程序

如果編寫的PL/SQL函數可以像Oracle內置函數一樣被SQL調用,需要遵循一定的規則:
- 所有函數的參數必須是IN模式。
- 參數的數據類型和RETURN子句的返回類型必須能被Oracle數據庫識別,這是因爲PL/SQL兼容所有的Oracle數據類型,但是PL/SQL擴充了自己的類型,比如BOOLEAN、INTEGER、記錄、集合、自定義的子類型等。
- 函數必須存儲在數據庫中,在客戶端PL/SQL環境中定義的PL/SQL函數是不能 被SQL語句調用的到的。
- 函數不能修改數據庫表,不能執行DDL語句如CREATE TABLED、DROP INDEX,不能執行DML語句如INSERT、DELETE、UPDATE、MERGE等,不能使用COMMIT或ROLLBACK提交或回滾事務。不過 當函數定義在自治事務中時,這是限制會稍稍寬鬆一些。因爲在自治事務中會與調用事務獨立出來。
- 當調用遠程的函數或通過並行行爲調用其他會話中的函數時,函數可能讀取或寫入包變量中的值,因爲Oracle服務器不支持跨用戶會話訪問。
- 僅當函數在一個SELECT列表中被調用時,後者是VALUES或SET子句中,函數才能夠更新包變量的值,如果在WHERE或GROUP BY子句中,它可能無法改寫包變量的值。
- 函數不能調用其他模塊,比如說存儲過程或函數,否則將打斷任何先前定義的規則。
- 函數不能引用一個視圖。

嵌套子程序

內嵌子程序是一個過程或函數,定義在PL/SQL塊聲明區中,僅能被這個快調用。
嵌套子程序的定義跟普通子程序定義相似,只是因爲它不需要被單獨地存儲在數據字典中,因此不需要使用CERAETE OR REPLACE語句,只需要直接使用FUNCTION或PROCEDURE開始定義即可。
嵌套子程序與存儲子程序的異同:

存儲子程序 嵌套子程序
子程序以編譯的形式存儲在數據字典中,當調用過程時,不需要重新編譯 嵌套子程序被編譯爲包含它的語句塊的一部分,如果包含它的語句塊是匿名的,並多次運行,那麼每一次都必須編譯子程序
子程序可以從對該子程序具有EXECUTE權限的用戶所提交的任何語句塊中調用 嵌套子程序只能從包含子程序的語句塊中調用
通過將自稱曾許代碼與調用代碼塊分開,使調用塊更短,更容易理解和維護 子程序和調用塊是完全相同的,這會導致混亂,如果對調用塊進行了更改,那麼子程序也將被編譯爲包含它的重編譯塊的一部分
可以使用DBMS_SHARED_POOL.KEEP包過程把已編譯僞代碼鎖定在共享池中以便重用,這樣可以改進其性能 嵌套子程序不能被其他子程序鎖定在共享池中
獨立的存儲子程序不能重載,但是包中的子程序可以在同一個包中重載 嵌套子程序可以在同一個語句塊中重載

子程序的前向聲明

PL/SQL要求必須要在調用子程序之前先定義好子程序,這在大多數情況下並沒有問題,但是當遇到子程序相互調用時,情況就會變得有些複雜。
比如,子程序A調用子程序B,但是子程序B又調用子程序A:

DECLARE
   v_val BINARY_INTEGER:=5;
   PROCEDURE A(p_counter IN OUT BINARY_INTEGER) IS          --聲明嵌套子程序A
   BEGIN
       B(p_counter);                                      --在嵌套子程序中調用B
   END A;
   PROCEDURE B(p_counter IN OUT BINARY_INTEGER) IS          --聲明嵌套子程序B
   BEGIN
      A(p_counter);                                          --在嵌套子程序中調用A
   END B;
BEGIN
   B(v_val);                                                 --調用嵌套子程序B
END;

上述代碼會產生一個異常:PLS-00313:在此作用域中沒有聲明'B'
此時可以使用前向聲明,或者稱爲預聲明。前向聲明僅包含子程序的結構定義,並不包含具體的實現代碼,這種聲明也用於包中的包頭中,如下代碼可以順利編譯通過:

DECLARE
   v_val BINARY_INTEGER:=5;
   PROCEDURE B(p_counter IN OUT BINARY_INTEGER);            --前向聲明嵌套子程序B
   PROCEDURE A(p_counter IN OUT BINARY_INTEGER) IS          --聲明嵌套子程序A
   BEGIN
       B(p_counter);                                      --在嵌套子程序中調用B
   END A;
   PROCEDURE B(p_counter IN OUT BINARY_INTEGER) IS          --聲明嵌套子程序B
   BEGIN
       A(p_counter);                                          --在嵌套子程序中調用A
   END B;
BEGIN
   B(v_val);                                                 --調用嵌套子程序B
END;

重載子程序

重載是面向對象的編程語言中非常常見的一種編寫對象方法的方式,重載是指具有相同名稱的方法,但是方法簽名(參數的類型或順序或者是數據類型)不同。在PL/SQL的包中,可以編寫重載特性的子程序。
重載的幾點限制:
- 只有本地貨包中的子程序,或者是對象中的方法纔可以被重載,不能對使用CREATE OR REPLACE的子程序進行重載。
- 只是參數的名稱不同,或者是參數的傳遞模式不同的子程序不能重載。
- 如果形式參數類型是屬於同一基類的子類,那麼也不能重載,比如INTEGER和REAL就同屬於NUMBER類型。
- 不能對只是返回值不同的兩個函數進行重載。

子程序自治事務

當進行子程序開發時,如果想獨立於主事務開始一個獨立的事務,在子程序中使用COMMIT或ROLLBACK語句時,不影響主事務的狀態,這種事務稱爲自治事務。
自治事務是由主事務開啓的獨立事務,自治事務把主事務掛起來,然後執行SQL操作,在提交或回滾這些操作後,重新恢復主事務。

自治事務使用AUTONOMOUS_TRANSACTION編譯提示來定義,這個編譯提示會讓PL/SQL編譯器把子程序或數據庫觸發器標記爲自治事務。可以將這個指令放到程序聲明部分的任何地方,但是爲了良好的可讀性,一般把他放到聲明區的頂部,基本語法如下:

PRAGMA AUTONOMOUS_TRANSACTION;

遞歸調用子程序

遞歸算法中有如下兩個原則:
- 必須要有一個方法能夠退出遞歸循環,以免永遠遞歸下去。如果遞歸程序無限制地執行下去,PL/SQL最終會用光內存然後拋出預定義的STORAGE_ERROR異常。
- 要有一個方法調用自身,通過不斷地改變條件來使得遞歸接近退出的位置。

遞歸最經典的示例就是階乘了,代碼如下:

DECLARE
  v_result INTEGER;
  FUNCTION fac(n POSITIVE)
       RETURN INTEGER IS                       --階乘的返回結果
   BEGIN
      IF n=1 THEN                              --如果n=1則終止條件
         RETURN 1;                         
      ELSE
         RETURN n*fac(n-1);                      --否則進行遞歸調用自身
      END IF;
   END fac; 
BEGIN
  v_result:= fac(10);                          --調用階乘函數
  DBMS_OUTPUT.put_line('結果是:'||v_result); --輸出階乘結果
END;

在使用PL/SQL進行開發時,通常使用遞歸來查詢層次結構的數據,例如emp表中的empno和mgr是兩個自引用的字段,mgr是當前empno的上級,mgr本身也是emp表中的一個員工,也具有自己的mgr,因此形成了樹狀的層次結構。
下面的代碼使用遞歸算法查詢某特定的員工編號下面的所有員工列表:

DECLARE
    PROCEDURE find_staff (mgr_no NUMBER, tier NUMBER := 1)
    IS
       boss_name   VARCHAR2 (10);                  --定義老闆的名稱
       CURSOR c1 (boss_no NUMBER)                  --定義遊標來查詢emp表中當前編號下的員工列表
       IS
          SELECT empno, ename
            FROM emp
           WHERE mgr = boss_no;
    BEGIN
       SELECT ename INTO boss_name FROM emp
        WHERE empno = mgr_no;                      --獲取管理者名稱
       IF tier = 1                                 --如果tier指定1,表示從最頂層開始查詢
       THEN
          INSERT INTO staff                          
               VALUES (boss_name || ' 是老闆 ');   --因爲第1層是老闆,下面的纔是經理
       END IF;
       FOR ee IN c1 (mgr_no)                       --通過遊標FOR循環向staff表插入員工信息
       LOOP
          INSERT INTO staff
               VALUES (   boss_name
                       || ' 管理 '
                       || ee.ename
                       || ' 在層次 '
                       || TO_CHAR (tier));
          find_staff (ee.empno, tier + 1);        --在遊標中,遞歸調用下層的員工列表
       END LOOP;
       COMMIT;
    END find_staff;
BEGIN
  find_staff(7839);                           --查詢7839的管理下的員工的列表和層次結構
END;

實際上這個例子也可以通過SQL語句的CONNECT BY子句來完成,但是通過使用子過程的遞歸調用方式,是的在進行PL/SQL編程時又多了一項利器,這在一些特殊的需要使用遞歸調用的場合非常有用。

理解子程序的依賴性

在創建子程序時,往往需要引用到其他對象,比如過程insertdept需要更新dept表,那麼可以將過程insertdept稱爲對dept表的對象依賴,而將表dept稱爲被引用對象。以上依賴是直接依賴,如果insertdept過程調用了insertemp,而insertemp過程中需要更新emp表,那麼insertdept對emp表就是間接依賴。

查看依賴

對於直接依賴,可以通過視圖user_dependencies找出其直接依賴的引用對象。
如果要查詢間接依賴,需要先進行一番安裝:
1. 首先要執行一下Oracle安裝目錄下的u-tldtree.sql
F:\app\Administrator\product\11.2.0\dbhome_1\RDBMS\ADMIN\u-tldtree.sql。這個SQL將創建一系列的表和視圖,以及一個用來填充依賴性關係的deptree_fill過程。爲確保具有相應的創建視圖與過程的權限,請以SYSDBA用戶進行登錄。
2. 執行如下的語句,向deptree視圖中填充數據。
EXEC deptree_fill('TABLE', 'SCOTT', 'EMP');
一切操作完成後,就可以通過查詢deptree獲取到對emp表的直接或間接依賴。nested_level是依賴的層次數據,1表示直接依賴,2表示間接依賴。

查看對象有效性

user_objects字典中查詢子程序對象時,會看到一個status字段標識當前的子程序時候有效,可選的值爲INVALID和VALID,用來標識當前對象的有效性狀態。當對子程序直接或間接依賴的對象進修改之後,比如對依賴對象進行DDL操作,那麼存儲子程序就可能變得無效。

重新編譯子程序

如果一個依賴對象失效,PL/SQL引擎將自動視圖在下一次調用時重新編譯,但這並不總是有效的。
可以使用如下語句對過程重新編譯:ALTER PROCEDURE testProcedure COMPILE;

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