1. 程式人生 > >ORACLE PL/SQL程式設計之六: 把過程與函式說透(窮追猛打,把根兒都拔起!)

ORACLE PL/SQL程式設計之六: 把過程與函式說透(窮追猛打,把根兒都拔起!)

本篇主要內容如下:

6.1 引言

6.2 建立函式

6.3 儲存過程

6.3.1 建立過程

6.3.2 呼叫儲存過程

6.3.3 AUTHID

6.3.4 PRAGMA AUTONOMOUS_TRANSACTION

6.3.5 開發儲存過程步驟

6.3.6 刪除過程和函式

6.3.7 過程與函式的比較

 6.1 引言

過程與函式(另外還有包與觸發器)是命名的PL/SQL塊(也是使用者的方案物件),被編譯後儲存在資料庫中,以備執行。因此,其它PL/SQL塊可以按名稱來使用他們。所以,可以將商業邏輯、企業規則寫成函式或過程儲存到資料庫中,以便共享。

過程和函式統稱為PL/SQL子程式,他們是被命名的PL/SQL塊,均儲存在資料庫中,並通過輸入、輸出引數或輸入/輸出引數與其呼叫者交換資訊。過程和函式的唯一區別是函式總向呼叫者返回資料,而過程則不返回資料。在本節中,主要介紹:

1.   建立儲存過程和函式。

2.   正確使用系統級的異常處理和使用者定義的異常處理。

3.   建立和管理儲存過程和函式。

6.2 建立函式

  1. 建立函式

語法如下: 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

CREATE [OR REPLACEFUNCTION function_name

(arg1 [ { IN OUT IN OUT }] type1 [DEFAULT value1],

[arg2 [ { IN OUT IN OUT }] type2 [DEFAULT value1]],

......

[argn [ { IN OUT IN OUT }] typen [DEFAULT valuen]])

[ AUTHID DEFINER | CURRENT_USER ]

RETURN return_type

IS AS

<型別.變數的宣告部分>

BEGIN

執行部分

RETURN expression

EXCEPTION

異常處理部分

END

 function_name;   

   l         IN,OUT,IN OUT是形參的模式。若省略,則為IN模式。IN模式的形參只能將實參傳遞給形參,進入函式內部,但只能讀不能寫,函式返回時實參的值不變。OUT模式的形參會忽略呼叫時的實參值(或說該形參的初始值總是NULL),但在函式內部可以被讀或寫,函式返回時形參的值會賦予給實參。IN OUT具有前兩種模式的特性,即呼叫時,實參的值總是傳遞給形參,結束時,形參的值傳遞給實參。呼叫時,對於IN模式的實參可以是常量或變數,但對於OUT和IN OUT模式的實參必須是變數。

  l         一般,只有在確認function_name函式是新函式或是要更新的函式時,才使用OR REPALCE關鍵字,否則容易刪除有用的函式。

  例1.           獲取某部門的工資總和: 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

--獲取某部門的工資總和

CREATE OR REPLACE

FUNCTION get_salary(

Dept_no NUMBER,

Emp_count OUT NUMBER)

RETURN NUMBER

IS

V_sum NUMBER;

BEGIN

SELECT SUM(SALARY), count(*) INTO V_sum, emp_count

FROM EMPLOYEES WHERE DEPARTMENT_ID=dept_no;

RETURN v_sum;

EXCEPTION

WHEN NO_DATA_FOUND THEN

DBMS_OUTPUT.PUT_LINE('你需要的資料不存在!');

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);

END get_salary;

   2. 函式的呼叫

  函式宣告時所定義的引數稱為形式引數,應用程式呼叫時為函式傳遞的引數稱為實際引數。應用程式在呼叫函式時,可以使用以下三種方法向函式傳遞引數:

  第一種引數傳遞格式:位置表示法。

  即在呼叫時按形參的排列順序,依次寫出實參的名稱,而將形參與實參關聯起來進行傳遞。用這種方法進行呼叫,形參與實參的名稱是相互獨立,沒有關係,強調次序才是重要的。

  格式為:

1

argument_value1[,argument_value2 …]

  例2計算某部門的工資總和:

1

2

3

4

5

6

7

DECLARE

V_num NUMBER;

V_sum NUMBER;

BEGIN

V_sum :=get_salary(10, v_num);

DBMS_OUTPUT.PUT_LINE('部門號為:10的工資總和:'||v_sum||',人數為:'||v_num);

END;

  第二種引數傳遞格式:名稱表示法。

  即在呼叫時按形參的名稱與實參的名稱,寫出實參對應的形參,而將形參與實參關聯起來進行傳遞。這種方法,形參與實參的名稱是相互獨立的,沒有關係,名稱的對應關係才是最重要的,次序並不重要。

  格式為:

1

argument => parameter [,…]

  其中:argument 為形式引數,它必須與函式定義時所宣告的形式引數名稱相同parameter 為實際引數。

  在這種格式中,形勢引數與實際引數成對出現,相互間關係唯一確定,所以引數的順序可以任意排列。

  例3計算某部門的工資總和: 

1

2

3

4

5

6

7

DECLARE

V_num NUMBER;

V_sum NUMBER;

BEGIN

V_sum :=get_salary(emp_count => v_num, dept_no => 10);

DBMS_OUTPUT.PUT_LINE('部門號為:10的工資總和:'||v_sum||',人數為:'||v_num);

END;

  第三種引數傳遞格式:組合傳遞。

即在呼叫一個函式時,同時使用位置表示法和名稱表示法為函式傳遞引數。採用這種引數傳遞方法時,使用位置表示法所傳遞的引數必須放在名稱表示法所傳遞的引數前面。也就是說,無論函式具有多少個引數,只要其中有一個引數使用名稱表示法,其後所有的引數都必須使用名稱表示法。 

  例4

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

CREATE OR REPLACE FUNCTION demo_fun(

Name VARCHAR2,--注意VARCHAR2不能給精度,如:VARCHAR2(10),其它類似

Age INTEGER,

Sex VARCHAR2)

RETURN VARCHAR2

AS

V_var VARCHAR2(32);

BEGIN

V_var := name||':'||TO_CHAR(age)||'歲.'||sex;

RETURN v_var;

END;

DECLARE

Var VARCHAR(32);

BEGIN

Var := demo_fun('user1', 30, sex => '男');

DBMS_OUTPUT.PUT_LINE(var);

Var := demo_fun('user2', age => 40, sex => '男');

DBMS_OUTPUT.PUT_LINE(var);

Var := demo_fun('user3', sex => '女', age => 20);

DBMS_OUTPUT.PUT_LINE(var);

END;

  無論採用哪一種引數傳遞方法,實際引數和形式引數之間的資料傳遞只有兩種方法:傳址法和傳值法。所謂傳址法是指在呼叫函式時,將實際引數的地址指標傳遞給形式引數,使形式引數和實際引數指向記憶體中的同一區域,從而實現引數資料的傳遞。這種方法又稱作參照法,即形式引數參照實際引數資料。輸入引數均採用傳址法傳遞資料。

傳值法是指將實際引數的資料拷貝到形式引數,而不是傳遞實際引數的地址。預設時,輸出引數和輸入/輸出引數均採用傳值法。在函式呼叫時,ORACLE將實際引數資料拷貝到輸入/輸出引數,而當函式正常執行退出時,又將輸出形式引數和輸入/輸出形式引數資料拷貝到實際引數變數中。 

  3. 引數預設值

CREATE OR REPLACE FUNCTION 語句中宣告函式引數時可以使用DEFAULT關鍵字為輸入引數指定預設值。 

  例5 

1

2

3

4

5

6

7

8

9

10

11

CREATE OR REPLACE FUNCTION demo_fun(

Name VARCHAR2,

Age INTEGER,

Sex VARCHAR2 DEFAULT '男')

RETURN VARCHAR2

AS

V_var VARCHAR2(32);

BEGIN

V_var := name||':'||TO_CHAR(age)||'歲.'||sex;

RETURN v_var;

END;

 具有預設值的函式建立後,在函式呼叫時,如果沒有為具有預設值的引數提供實際引數值,函式將使用該引數的預設值。但當呼叫者為預設引數提供實際引數時,函式將使用實際引數值。在建立函式時,只能為輸入引數設定預設值,而不能為輸入/輸出引數設定預設值。

1

2

3

4

5

6

7

8

9

10

DECLARE

varVARCHAR(32);

BEGIN

Var := demo_fun('user1', 30);

DBMS_OUTPUT.PUT_LINE(var);

Var := demo_fun('user2', age => 40);

DBMS_OUTPUT.PUT_LINE(var);

Var := demo_fun('user3', sex => '女', age => 20);

DBMS_OUTPUT.PUT_LINE(var);

END;

6.3 儲存過程

6.3.1 建立過程

  建立儲存過程

  在 ORACLE SERVER上建立儲存過程,可以被多個應用程式呼叫,可以向儲存過程傳遞引數,也可以向儲存過程傳回引數.

  建立過程語法:

1

2

3

4

5

6

7

8

9

10

11

12

13

CREATE [OR REPLACEPROCEDURE procedure_name

([arg1 [ IN OUT IN OUT ]] type1 [DEFAULT value1],

[arg2 [ IN OUT IN OUT ]] type2 [DEFAULT value1]],

......

[argn [ IN OUT IN OUT ]] typen [DEFAULT valuen])

[ AUTHID DEFINER | CURRENT_USER ]

IS AS }

<宣告部分>

BEGIN

<執行部分>

EXCEPTION

<可選的異常錯誤處理程式>

END procedure_name;

  說明:相關引數說明參見函式的語法說明。

  例6使用者連線登記記錄; 

1

2

3

4

5

6

7

CREATE TABLE logtable (userid VARCHAR2(10), logdate date);

CREATE OR REPLACE PROCEDURE logexecution

IS

BEGIN

INSERT INTO logtable (userid, logdate) VALUES (USER, SYSDATE);

END;

 7刪除指定員工記錄; 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

CREATE OR REPLACE

PROCEDURE DelEmp

(v_empno IN employees.employee_id%TYPE)

AS

No_result EXCEPTION;

BEGIN

DELETE FROM employees WHERE employee_id = v_empno;

IF SQL%NOTFOUND THEN

RAISE no_result;

END IF;

DBMS_OUTPUT.PUT_LINE('編碼為'||v_empno||'的員工已被刪除!');

EXCEPTION

WHEN no_result THEN

DBMS_OUTPUT.PUT_LINE('溫馨提示:你需要的資料不存在!');

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);

END DelEmp;

  8插入員工記錄:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

CREATE OR REPLACE

PROCEDURE InsertEmp(

v_empno     in employees.employee_id%TYPE,

v_firstname in employees.first_name%TYPE,

v_lastname  in employees.last_name%TYPE,

v_deptno    in employees.department_id%TYPE

)

AS

empno_remaining EXCEPTION;

PRAGMA EXCEPTION_INIT(empno_remaining, -1);

/* -1 是違反唯一約束條件的錯誤程式碼 */

BEGIN

INSERT INTO EMPLOYEES(EMPLOYEE_ID, FIRST_NAME, LAST_NAME, HIRE_DATE,DEPARTMENT_ID)

VALUES(v_empno, v_firstname,v_lastname, sysdate, v_deptno);

DBMS_OUTPUT.PUT_LINE('溫馨提示:插入資料記錄成功!');

EXCEPTION

WHEN empno_remaining THEN

DBMS_OUTPUT.PUT_LINE('溫馨提示:違反資料完整性約束!');

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);

END InsertEmp;

9使用儲存過程向departments表中插入資料。 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

CREATE OR REPLACE

PROCEDURE insert_dept

(v_dept_id IN departments.department_id%TYPE,

v_dept_name IN departments.department_name%TYPE,

v_mgr_id IN departments.manager_id%TYPE,

v_loc_id IN departments.location_id%TYPE)

IS

ept_null_error EXCEPTION;

PRAGMA EXCEPTION_INIT(ept_null_error, -1400);

ept_no_loc_id EXCEPTION;

PRAGMA EXCEPTION_INIT(ept_no_loc_id, -2291);

BEGIN

INSERT INTO departments

(department_id, department_name, manager_id, location_id)

VALUES

(v_dept_id, v_dept_name, v_mgr_id, v_loc_id);

DBMS_OUTPUT.PUT_LINE('插入部門'||v_dept_id||'成功');

EXCEPTION

WHEN DUP_VAL_ON_INDEX THEN

RAISE_APPLICATION_ERROR(-20000, '部門編碼不能重複');

WHEN ept_null_error THEN

RAISE_APPLICATION_ERROR(-20001, '部門編碼、部門名稱不能為空');

WHEN ept_no_loc_id THEN

RAISE_APPLICATION_ERROR(-20002, '沒有該地點');

END insert_dept;

/*呼叫例項一:

DECLARE

ept_20000 EXCEPTION;

PRAGMA EXCEPTION_INIT(ept_20000, -20000);

ept_20001 EXCEPTION;

PRAGMA EXCEPTION_INIT(ept_20001, -20001);

ept_20002 EXCEPTION;

PRAGMA EXCEPTION_INIT(ept_20002, -20002);

BEGIN

insert_dept(300, '部門300', 100, 2400);

insert_dept(310, NULL, 100, 2400);

insert_dept(310, '部門310', 100, 900);

EXCEPTION

WHEN ept_20000 THEN

DBMS_OUTPUT.PUT_LINE('ept_20000部門編碼不能重複');

WHEN ept_20001 THEN

DBMS_OUTPUT.PUT_LINE('ept_20001部門編碼、部門名稱不能為空');

WHEN ept_20002 THEN

DBMS_OUTPUT.PUT_LINE('ept_20002沒有該地點');

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE('others出現了其他異常錯誤');

END;

呼叫例項二:

DECLARE

ept_20000 EXCEPTION;

PRAGMA EXCEPTION_INIT(ept_20000, -20000);

ept_20001 EXCEPTION;

PRAGMA EXCEPTION_INIT(ept_20001, -20001);

ept_20002 EXCEPTION;

PRAGMA EXCEPTION_INIT(ept_20002, -20002);

BEGIN

insert_dept(v_dept_name => '部門310', v_dept_id => 310,

v_mgr_id => 100, v_loc_id => 2400);

insert_dept(320, '部門320', v_mgr_id => 100, v_loc_id => 900);

EXCEPTION

WHEN ept_20000 THEN

DBMS_OUTPUT.PUT_LINE('ept_20000部門編碼不能重複');

WHEN ept_20001 THEN

DBMS_OUTPUT.PUT_LINE('ept_20001部門編碼、部門名稱不能為空');

WHEN ept_20002 THEN

DBMS_OUTPUT.PUT_LINE('ept_20002沒有該地點');

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE('others出現了其他異常錯誤');

END;

*/

 6.3.2 呼叫儲存過程

   儲存過程建立完成後,只要通過授權,使用者就可以在SQLPLUS 、ORACLE開發工具或第三方開發工具中來呼叫執行。對於引數的傳遞也有三種:按位置傳遞、按名稱傳遞和組合傳遞,傳遞方法與函式的一樣。ORACLE 使用EXECUTE 語句來實現對儲存過程的呼叫: 

EXEC[UTE] procedure_name( parameter1, parameter2…);

   10 

EXECUTE logexecution;

   11查詢指定員工記錄; 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

CREATE OR REPLACE

PROCEDURE QueryEmp

(v_empno IN  employees.employee_id%TYPE,

v_ename OUT employees.first_name%TYPE,

v_sal   OUT employees.salary%TYPE)

AS

BEGIN

SELECT last_name || last_name, salary INTO v_ename, v_sal

FROM employees

WHERE employee_id = v_empno;

DBMS_OUTPUT.PUT_LINE('溫馨提示:編碼為'||v_empno||'的員工已經查到!');

EXCEPTION

WHEN NO_DATA_FOUND THEN

DBMS_OUTPUT.PUT_LINE('溫馨提示:你需要的資料不存在!');

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);

END QueryEmp;

--呼叫

DECLARE

v1 employees.first_name%TYPE;

v2 employees.salary%TYPE;

BEGIN

QueryEmp(100, v1, v2);

DBMS_OUTPUT.PUT_LINE('姓名:'||v1);

DBMS_OUTPUT.PUT_LINE('工資:'||v2);

QueryEmp(103, v1, v2);

DBMS_OUTPUT.PUT_LINE('姓名:'||v1);

DBMS_OUTPUT.PUT_LINE('工資:'||v2);

QueryEmp(104, v1, v2);

DBMS_OUTPUT.PUT_LINE('姓名:'||v1);

DBMS_OUTPUT.PUT_LINE('工資:'||v2);

END;

  12計算指定部門的工資總和,並統計其中的職工數量。 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15