1. 程式人生 > >Oracle入門-2儲存過程

Oracle入門-2儲存過程

一、過程(PROCEDURE )
   過程是作為一個單獨的程式編譯到Oracle資料庫模式中的。過程能夠接收引數。在編譯過程時,Create Procedure語句的過程識別符號在資料字典中成為物件名。
過程結構如下:
    CREATE  OR  REPLACE  PROCEDURE   過程名  (可選引數)   IS
          宣告部分
    BEGIN
          程式體
    EXCEPTION
          異常處理程式
    END  過程名
過程的命名應該用動詞。因為過程通常是執行某種動作,比如,更新資料庫、寫檔案,或者傳送訊息。
過程並不一定具有引數。當建立的過程沒有引數時,就不需要使用圓括號。當呼叫過程時空括號是可選的。
可以編碼為IS或AS,兩種語法都是合法的。
Sql程式碼
複製程式碼
  1. PROCEDURE INSERT_TEMP IS (| AS)  
PROCEDURE INSERT_TEMP IS (| AS)

儘管將過程名追加到END子句後是可選的,但強烈推薦這麼做。
例如:
Sql程式碼 複製程式碼
  1. Createtabletemp(n number);  
Create table temp(n number);

定義一個過程:
Sql程式碼 複製程式碼
  1. PROCEDURE INSERT_TEMP IS
  2. BEGIN
  3.    INSER INTOTEMP (n) VALUES (0);   
  4. END INSERT_TEMP;  
PROCEDURE INSERT_TEMP IS
BEGIN
   INSER INTO TEMP (n) VALUES (0);
END INSERT_TEMP;

    常見的過程樣式是將IS、BEGIN、EXCEPTION和END對齊。這些關鍵字作用域內的全部程式碼都要進行縮排。
Sql程式碼 複製程式碼
  1. PROCEDURE  PRINT_TEMP    
  2. IS
  3.     v_average  NUMBER;   
  4.     v_sum  NUMBER;   
  5. BEGIN
  6. SELECTAVG(N), SUM(N)  INTO  v_average,v_sum FROMTEMP;   
  7.     dbms_output.put_line(‘Average:’ || v_average );   
  8.     dbms_output.put_line(‘Sum:’ || v_sum);   
  9. END  PRINT_TEMP;  
PROCEDURE  PRINT_TEMP 
IS
    v_average  NUMBER;
    v_sum  NUMBER;
BEGIN
    SELECT  AVG(N), SUM(N)  INTO  v_average,v_sum FROM TEMP;
    dbms_output.put_line(‘Average:’ || v_average );
    dbms_output.put_line(‘Sum:’ || v_sum);
END  PRINT_TEMP;

    單獨的過程常會開發成為一個新包或者一個已有的包合併。將INSERT_TEMP合併到一個包中,僅需要進行下面簡單的編輯工作:
1. 首先在ORACLE建立PACKAGE
Sql程式碼 複製程式碼
  1. CREATEORREPLACE  PACKAGE  TEMP_OP  IS
  2. PROCEDURE  INSERT_TEMP;   
  3. END  TEMP_OP;  
CREATE  OR  REPLACE  PACKAGE  TEMP_OP  IS
       PROCEDURE  INSERT_TEMP;
END  TEMP_OP;

2. 建立PACKAGE BODY
Sql程式碼 複製程式碼
  1. CREATEORREPLACE  PACKAGE  BODY  TEMP_OP  IS
  2. PROCEDURE  INSERT_TEMP  IS
  3. BEGIN
  4. INSERTINTOtemp(n)  VALUES (0);   
  5. END INSERT_TEMP;   
  6. END  TEMP_OP;  
CREATE  OR  REPLACE  PACKAGE  BODY  TEMP_OP  IS
       PROCEDURE  INSERT_TEMP  IS
             BEGIN
                  INSERT INTO temp(n)  VALUES (0);
       END INSERT_TEMP;
END  TEMP_OP;

二、函式(FUNCTION)
   包通常扮演API的角色,隱藏物件,提供物件上的操作。而函式常扮演物件狀態資訊的選擇器。
設想一個要計算物件的某個屬性值的函式。函式不是動作者,而是狀態的計算值。所以應該用名詞對函式進行命名。
FUNCTION  student_status  (可選引數)   RETURN VARCHAR2 IS
    宣告部分
BEGIN
    子程式體Program body
    RETURN expression;
EXCEPTION
    異常處理程式,其中應該包括一條RETURN語句
END  student_status;
引數是可選的,但是RETURN 語句卻是必須具備的,FUNCTION語句必須包括一個RETURN 和型別。
說明:
1.宣告部分
宣告變數,需要返回的變數也是在這裡宣告的。函式必須具有返回值。如果函式的返回值是一個NUMBER,則該NUMBER變數就在這裡宣告。該變數應該出現在RETURN語句中。
2.子程式體
支援迴圈、if-then-else結構、case語句和declare-block 結構。程式體必須包括RETURN語句。
3.異常處理程式
可選的,可以編寫用於特定型別錯誤的異常處理程式或者是通用的異常處理程式,確定異常處理程式中包含了RETURN語句。
例如:
Sql程式碼 複製程式碼
  1. CREATEORREPLACEFUNCTION   tomorrow   RETURNDATE
  2. IS
  3.      next_day  DATE;     
  4. BEGIN
  5.      next_day := SYSDATE +1 ;   
  6. RETURN  next_day;   
  7. END  tomorrow;  
CREATE  OR  REPLACE  FUNCTION   tomorrow   RETURN   DATE 
IS
     next_day  DATE;  
BEGIN
	 next_day := SYSDATE +1 ;
     RETURN  next_day;
END  tomorrow;

不宣告變數,可以直接簡寫為:
Sql程式碼 複製程式碼
  1. CREATEORREPLACEFUNCTION   tomorrow   RETURNDATEIS
  2. BEGIN
  3. RETURN  SYSDATE +1 ;   
  4. END  tomorrow;  
CREATE  OR  REPLACE  FUNCTION   tomorrow   RETURN   DATE  IS
BEGIN
	 RETURN  SYSDATE +1 ;
END  tomorrow;

如果函式沒有引數,則不要在函式定義時使用空括號。這規則同樣適用於過程。
使用函式:
Sql程式碼 複製程式碼
  1. CREATEORREPLACE   sample   
  2. IS
  3.     today   DATE;   
  4. BEGIN
  5.     today := tomorrow – 1;   
  6. dbms_output.put_line(tomorrow - 1);   
  7. END sample;  
CREATE  OR  REPLACE   sample
IS
    today   DATE;
BEGIN
    today := tomorrow – 1;
dbms_output.put_line(tomorrow - 1);
END sample;

三、包(PACKAGE)
    利用包提供這樣一套機制:將較小的程式單元在邏輯上組合在一起。這種由過程到包的組合就是程式碼的模組化。包的使用意味著只需要管理更少的檔案和更少的模組。對於程式設計師來說更容易做到模組的重用。
遷移過程包括將這些過程體分別複製到同一個包體中。過程介面定義成為包規範。最後可以通過新增新的過程和函式來加強包的整體功能。
完成包的合併後,首先編譯包規範,然後編譯包體。
例如:
Sql程式碼 複製程式碼
  1. PACKAGE   application_name   IS
  2. PROCEDURE  p1;   
  3. PROCEDURE  p2;   
  4. END application_name;   
  5. PACKAGE  BODY  application_name  IS
  6. PROCEDURE  p1  IS
  7. BEGIN
  8.          PL/SQL  code   
  9. END  p1;   
  10. PROCEDURE  p2  IS
  11. BEGIN
  12.          PL/SQL  code   
  13. END  p2;   
  14. END application_name;  
PACKAGE   application_name   IS
     PROCEDURE  p1;
     PROCEDURE  p2;
END application_name;

PACKAGE  BODY  application_name  IS
     PROCEDURE  p1  IS
     BEGIN
         PL/SQL  code
     END  p1;
     PROCEDURE  p2  IS
     BEGIN
         PL/SQL  code
     END  p2;
END application_name;

四、包規範
    PL/SQL語言要求將一個程式集合的介面編譯成為單一的程式單元。這個單元,也就是包規範。這只是定義了API介面。而應用邏輯的具體實現則包含在包體中。
包規範可以是一個單獨的ASCII文字檔案,能編譯成單一程式單元。包體也可以是一個單獨的ASCII文字檔案。必須首先成功編譯包規範,然後才能編譯包體。可以把包規範和包體放入同一個檔案中。
1.語法與格式
最基本的包規範語法是:
Sql程式碼 複製程式碼
  1. CREATE  PACKAGE  package_name  IS
  2.      Type  definitions  for  records, index-by  tables, varrays, nested  tables    
  3. Constants   
  4. Exceptions   
  5. Global  variable  declarations   
  6. PROCEDURE  procedure_name_1  (parameters & types);   
  7. FUNCTION  function_name_1 (parameters & types)  RETURN  type;   
  8. END package_name;  
CREATE  PACKAGE  package_name  IS
     Type  definitions  for  records, index-by  tables, varrays, nested  tables 
Constants
Exceptions
Global  variable  declarations
PROCEDURE  procedure_name_1  (parameters & types);
FUNCTION  function_name_1 (parameters & types)  RETURN  type;
END package_name;

   包規範對過程和函數出現的順序沒有要求。而且包規範中的每個子程式都必須有一個與之相對應的子程式體。
包規範可以宣告資料型別,資料宣告和異常。在包規範中宣告的所有資料物件都是全域性的。所以在包規範宣告的變數只是那些作用域是全域性的變數。
    包體中的PROCEDURE語句必須與相應包規範中的PROCEDURE語句相匹配。包括子程式名稱、引數名稱、引數模式和引數型別等。這一要求同樣適用於FUNCTION;
包規範可以宣告異常。異常或者全部宣告在規範開頭,或者全部宣告在規範結尾。例如:
Sql程式碼 複製程式碼
  1. CREATE  PACKAGE  package_name  IS
  2.     Invalie_operation   EXCEPTOIN;   
  3. PROCEDURE procedure_name_1 ( parameters  &  types);   
  4.     …   
  5. END   package_name;  
CREATE  PACKAGE  package_name  IS
	Invalie_operation   EXCEPTOIN;
	PROCEDURE procedure_name_1 ( parameters  &  types);
	…
END   package_name;

處理異常的應用程式程式碼類似於:
Sql程式碼 複製程式碼
  1. BEGIN
  2.     other  code, etc   
  3.     package_name.procedure_name_1(parameters);   
  4.          other  code, etc   
  5. EXCEPTION   
  6. WHEN  package_name.invalid_operation  THEN  do something;     
  7. END;  
BEGIN
	other  code, etc
	package_name.procedure_name_1(parameters);
         other  code, etc
EXCEPTION
	WHEN  package_name.invalid_operation  THEN  do something;  
END;

五、引數與模式
PL/SQL有三種模式:
1. IN(預設)
    傳給子程式的IN模式引數表明 了子程式只能將該引數作為一個常量來使用。這是隻讀的。作為IN模式的引數可以是一個文字表達式、常量宣告或者變數宣告。當引數為變數時,該模式提供了安全措施保證正確的程式呼叫。呼叫程式能夠了解在完成呼叫後,該變數的值沒有發生改變。
下面的過程不能編譯,原因是對IN模式變數進行了寫操作。
Sql程式碼 複製程式碼
  1. PROCEDURE print_next_value (   
  2.     v_data  ININTEGER
  3. )   
  4. BEGIN
  5.     v_data  := v_data +1 ;--compile error
  6.     dbms_output.put_line(v_data);    
  7. dbms_output.put_line(v_data + 1); --compile correct
  8. END;  
PROCEDURE print_next_value (
	v_data  IN  INTEGER	
)
BEGIN
	v_data  := v_data +1 ;--compile error
	dbms_output.put_line(v_data); 
dbms_output.put_line(v_data + 1); --compile correct
END;

2. IN  OUT
   能夠通過這種模式傳遞的引數只能是變數型別,不允許為文字或者常量。前提是被呼叫的過程將會改變傳遞的內容。被調過程也能對其進行讀寫操作。
當檢視一個具有IN OUT模式引數的過程時,要求呼叫程式在呼叫該過程時必須提供資料。這是IN OUT引數中IN部分的要求。
例如:
Sql程式碼 複製程式碼
  1. PROCEDURE  change_data (   
  2.     v_data  INOUTINTEGER
  3. )  IS
  4. BGIN   
  5. for  i   in   1..10   loop   
  6.         v_data := v_data +1;   
  7. end loop;   
  8. END  change_data;  
PROCEDURE  change_data (
	v_data  IN  OUT   INTEGER
)  IS
BGIN
	for  i   in   1..10   loop
 		v_data := v_data +1;
    end loop;
END  change_data;

塊呼叫
Sql程式碼 複製程式碼
  1. DECLARE
  2.     my_data  INTEGER :=0;--不能為常量
  3. BEGIN
  4.     change_data(my_data);   
  5.     dbms_output.put_line(‘block print:’  ||  my_data);--10
  6. END;  
DECLARE
	my_data  INTEGER :=0;--不能為常量
BEGIN
	change_data(my_data);
	dbms_output.put_line(‘block print:’  ||  my_data);--10
END;

3. OUT
    能夠通過這種模式傳遞引數只能是變數型別。不允許為文字或者常量。在子程式中,一個OUT模式引數的初始值為NULL。使用OUT模式引數的目的在於傳遞關於介面的資訊。呼叫過程不必為被調過程傳遞引數。被調過程完成對資料結構的讀寫操作。
例如:
Sql程式碼 複製程式碼
  1. PROCEDURE provide_data(   
  2.     v_data   OUTINTEGER
  3. )  IS
  4. BEGIN
  5.     v_data := 100;   
  6. for  i   in  1..10  loop   
  7.         v_data := v_data +1;   
  8. end loop;    
  9. END  provide_data;  
PROCEDURE provide_data(
	v_data   OUT   INTEGER
)  IS
BEGIN
	v_data := 100;
	for  i   in  1..10  loop
    	v_data := v_data +1;
end loop; 
END  provide_data;

塊中呼叫
Sql程式碼 複製程式碼
  1. DECLARE
  2.     my_data   INTEGER :=0;   
  3. BEGIN
  4. insertintotempvalues(my_data);--0
  5.     provide_data(my_data) ;   
  6. insertintotempvalues(my_data);--110
  7. END;  
DECLARE
	my_data   INTEGER :=0;
BEGIN
	insert  into temp values(my_data);--0
	provide_data(my_data) ;
	insert  into temp values(my_data);--110
END;

六、函式與模式
    函式常常用名詞來命名,而過程則常用動詞來命名。在所有應用程式中,絕大多數的函式引數都是IN 模式的。但是函式引數的模式可以是所有這3種模式。
下面展示了關於一個函式的設計,該函式返回資料和狀態資訊。對於這個介面,假定ARG——1是主鍵,用來精確確定需要獲取的記錄。引數next_rec是需要的資料。
Sql程式碼 複製程式碼
  1. FUNCTION   next_rec( arg1   IN   type,  next_record  OUT   type)   
  2. RETURN BOOLEAN;  
FUNCTION   next_rec( arg1   IN   type,  next_record  OUT   type)
RETURN BOOLEAN;

這種設計允許使用者編寫如下程式碼:
Sql程式碼 複製程式碼
  1. WHILE ( next_rec(arg1,my_record_structure) )   
  2. LOOP   
  3.     process  my_record_structure;   
  4. END  LOOP;  
WHILE ( next_rec(arg1,my_record_structure) )
LOOP
	process  my_record_structure;
END  LOOP;

可以通過下面的過程來代替函式:
Sql程式碼 複製程式碼
  1. PROCEDURE  get_next_rec(   
  2. arg1         IN   type,   
  3. next_record  OUT  type,   
  4. status       OUT  BOOLEAN   
  5. );  
PROCEDURE  get_next_rec(
arg1         IN   type,
next_record  OUT  type,
status       OUT  BOOLEAN
);

使用:
Sql程式碼 複製程式碼
  1. LOOP   
  2.     get_next_rec(arg1,my_record_structure,status);   
  3.     EXIT  WHENNOT  status;   
  4.     process   my_record_structure;   
  5. END   LOOP;  
LOOP
	get_next_rec(arg1,my_record_structure,status);
    EXIT  WHEN  NOT  status;
	process   my_record_structure;
END   LOOP;

    從塊角度來看,沒有什麼大的不同。在概念上,函式是一個返回值為下一個記錄的選擇器,但實際上只是隨意提供了關於獲取操作的狀態。過程的作用更像是一個服務,獲取一條記錄,並返回該記錄和狀態。
七、命名錶示法與位置表示法
考慮到如下介面定義的過程:
Sql程式碼 複製程式碼
  1. PROCEDURE  proc_name (arg1  mode  and type, arg2  mode  and  type);  
PROCEDURE  proc_name (arg1  mode  and type, arg2  mode  and  type);

使用者有兩種語法可供選擇。第一種是POSITIONAL表示法,第二種是NAMED表示法:
Sql程式碼 複製程式碼
  1. 1.proc_name(variable_1,varialble_2);   
  2. 2.proc_name(arg1 => variable_1,arg2 => variable_2);  
1.proc_name(variable_1,varialble_2);
2.proc_name(arg1 => variable_1,arg2 => variable_2);

形參名(Formal parameter name)指的是在過程或者函式介面定義中所使用的名稱。對於前面的proc_name過程來說,它的形參名是ARG1和ARG2。
形參名應該是泛化的,而且應該表達出該引數的用途。
定義一過程:
Sql程式碼 複製程式碼
  1. PROCEDURE  get_record(   
  2.     file_id      ININTEGER,   
  3.     Record_read  OUT  VARCHAR2   
  4. );  
PROCEDURE  get_record(
	file_id      IN   INTEGER,
	Record_read  OUT  VARCHAR2
);

使用者可以用兩種不同格式來呼叫該過程。
位置表示法:
Sql程式碼 複製程式碼
  1. DECLARE
  2.     file_id  INTEGER;   
  3.          next_payroll_record  VARCHAR2(100);   
  4. BEGIN
  5.     get_record(field_id,next_payroll_record);   
  6. END;  
DECLARE
	file_id  INTEGER;
         next_payroll_record  VARCHAR2(100);
BEGIN
	get_record(field_id,next_payroll_record);
END;

第二種命名錶示法:
Sql程式碼 複製程式碼
  1. DECLARE
  2.     file_id  INTEGER;   
  3.          next_payroll_record  VARCHAR2(100);   
  4. BEGIN
  5.     get_record(file_id=>file_id,   
  6.                     record_read=>next_payroll_record);   
  7. END;  
DECLARE
	file_id  INTEGER;
         next_payroll_record  VARCHAR2(100);
BEGIN
	get_record(file_id=>file_id,
                    record_read=>next_payroll_record);
END;

適合命名錶示法的情況:
(1) 如果所選定的變數名稱不能充分表達它們的用途,則適合採用命名錶示法。
(2) 如果子程式的編碼要用到預設值而且只用到某些預設值,則適合採用命名錶示法。
考慮如下合計薪水的過程。該子程式的第個引數都有預設值:
Sql程式碼 複製程式碼
  1. CREATEORREPLACEFUNCTION  aggregate_salary(   
  2.     Monthly_base   NUMBER :=10000,   
  3.     No_of_months   INTEGER :=12   
  4. )  RETURN   NUMBER   
  5. IS
  6. BEGIN
  7. return (monthly_base  *  no_of_months);   
  8. END;  
CREATE  OR  REPLACE  FUNCTION  aggregate_salary(
	Monthly_base   NUMBER :=10000,
	No_of_months   INTEGER :=12
)  RETURN   NUMBER
IS
BEGIN
	return (monthly_base  *  no_of_months);
END;

呼叫塊:
Sql程式碼 複製程式碼
  1. DECLARE
  2.     no_of_months  INTEGER :=10;   
  3.     aggregate   NUMBER;   
  4. BEGIN
  5. --salary for 10 months
  6. aggregate := aggregate_salary(no_of_months=>no_of_months);   
  7. END;  
DECLARE
	no_of_months  INTEGER :=10;
	aggregate   NUMBER;
BEGIN
	--salary for 10 months
aggregate := aggregate_salary(no_of_months=>no_of_months);
END;

八、預設引數
    過程或者函式的規範可以為引數定義一個模式型別為IN或者IN OUT的預設值。下面給出了兩種語法形式:
Sql程式碼 複製程式碼
  1. PROCEDUREname(argument   mode   datatype  :=  a_default_value);   
  2. PROCEDUREname(argument   mode   datatype  DEFAULT  a_default_value);  
PROCEDURE  name(argument   mode   datatype  :=  a_default_value);
PROCEDURE  name(argument   mode   datatype  DEFAULT  a_default_value);

例如:
Sql程式碼 複製程式碼
  1. FUNCTION  circle(radius  IN  NUMBER  :=  1)  RETURN  NUMBER  IS
  2. BEGIN
  3. RETURN  3.14 * radius ** 2;   
  4. END;   
  5. FUNCTION  circle(radius  IN  NUMBER  DEFAULT 1)  RETURN  NUMBER IS
  6. BEGIN
  7. RETURN  3.14 * radius ** 2;   
  8. END;  
FUNCTION  circle(radius  IN  NUMBER  :=  1)  RETURN  NUMBER  IS
BEGIN
	RETURN  3.14 * radius ** 2;
END;
FUNCTION  circle(radius  IN  NUMBER  DEFAULT 1)  RETURN  NUMBER IS
BEGIN
	RETURN  3.14 * radius ** 2;
END;

   當子程式包含多個預設的引數時,使用者可以選擇其中任何一個引數的預設值,只是可能需要採用命名錶示法。
九、%TYPE
%TYPE語法用於宣告一個變數,該變數的型別是從資料庫表中某列的型別派生而來的。這種型別定義的語法如下所示:
Sql程式碼 複製程式碼
  1. Variable_name   table_name.column_name%TYPE;  
Variable_name   table_name.column_name%TYPE;


十、取結果集
Sql程式碼 複製程式碼
  1. createorreplace package CQ_SJ_OA as
  2.    TYPE RESULTSET IS REF CURSOR;   
  3. PROCEDURE PW_CQSJOA_DW_LIST(   
  4.              PN_PARENT_ID       INTEGER,      -- 父單位Id
  5.              P_RESULT      OUT  RESULTSET     --子單位列表
  6.    );   
  7. end CQ_SJ_OA;  
create or replace package CQ_SJ_OA as
   
   TYPE RESULTSET IS REF CURSOR;
   
   PROCEDURE PW_CQSJOA_DW_LIST(
             PN_PARENT_ID       INTEGER,      -- 父單位Id
             P_RESULT      OUT  RESULTSET     --子單位列表
   );
   
end CQ_SJ_OA;
Sql程式碼 複製程式碼
  1. createorreplace package body CQ_SJ_OA as
  2. PROCEDURE PW_CQSJOA_DW_LIST   
  3.              (   
  4.                 PN_PARENT_ID       INTEGER,    --父單位
  5.                 P_RESULT    OUT  RESULTSET     --子單位列表
  6.              )   
  7. AS
  8. BEGIN
  9. OPEN P_RESULT FOR
  10. SELECT * FROM MAG_COMPANY WHERE PARENT_ID = PN_PARENT_ID;   
  11. END;   
  12. end CQ_SJ_OA;  
create or replace package body CQ_SJ_OA as
   
   PROCEDURE PW_CQSJOA_DW_LIST
             (
                PN_PARENT_ID       INTEGER,    --父單位
                P_RESULT    OUT  RESULTSET     --子單位列表
             )
   AS
   BEGIN
      OPEN P_RESULT FOR 
        SELECT * FROM MAG_COMPANY WHERE PARENT_ID = PN_PARENT_ID;
    
   END;

end CQ_SJ_OA;

十一、PL/SQL中除錯oracle儲存過程
   點選要除錯的儲存過程,右鍵選擇TEST。如果需要檢視變數,當然除錯都需要。在右鍵選單中選擇Add debug information。start debugger(F9)開始我們的測試,Run(Ctrl+R) 隨時在varible List中輸入我們想檢視的變數
其它:
Step into(Ctrl+N):單步除錯,進入下一步
Step over(Ctrl+O):結束該語句
Step out(Ctrl+T):跳出該過程
Run to next exception:進入下一個斷點Breakpoint
Toggle Breakpoint設定斷點:游標指向一行,Ctrl+B;或滑鼠點中該行的行號邊緣。