存儲過程和函數也是一種PL/SQL塊,是存入數據庫的PL/SQL塊。但存儲過程和函數不同於已經介紹過的PL/SQL程序,我們通常把PL/SQL程序稱爲無名塊,而存儲過程和函數是以命名的方式存儲於數據庫中的。和PL/SQL程序相比,存儲過程有很多優點,具體歸納如下:
* 存儲過程和函數以命名的數據庫對象形式存儲於數據庫當中。存儲在數據庫中的優點是很明顯的,因爲代碼不保存在本地,用戶可以在任何客戶機上登錄到數據庫,並調用或修改代碼。
* 存儲過程和函數可由數據庫提供安全保證,要想使用存儲過程和函數,需要有存儲過程和函數的所有者的授權,只有被授權的用戶或創建者本身才能執行存儲過程或調用函數。
* 存儲過程和函數的信息是寫入數據字典的,所以存儲過程可以看作是一個公用模塊,用戶編寫的PL/SQL程序或其他存儲過程都可以調用它(但存儲過程和函數不能調用PL/SQL程序)。一個重複使用的功能,可以設計成爲存儲過程,比如:顯示一張工資統計表,可以設計成爲存儲過程;一個經常調用的計算,可以設計成爲存儲函數;根據僱員編號返回僱員的姓名,可以設計成存儲函數。
* 像其他高級語言的過程和函數一樣,可以傳遞參數給存儲過程或函數,參數的傳遞也有多種方式。存儲過程可以有返回值,也可以沒有返回值,存儲過程的返回值必須通過參數帶回;函數有一定的數據類型,像其他的標準函數一樣,我們可以通過對函數名的調用返回函數值。
存儲過程和函數需要進行編譯,以排除語法錯誤,只有編譯通過才能調用。
創建和刪除存儲過程
創建存儲過程,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系統權限。該權限可由系統管理員授予。創建一個存儲過程的基本語句如下:
CREATE [OR REPLACE] PROCEDURE 存儲過程名[(參數[IN|OUT|IN OUT] 數據類型…)]
{AS|IS}
[說明部分]
BEGIN
可執行部分
[EXCEPTION
錯誤處理部分]
END [過程名];
其中:
可選關鍵字OR REPLACE 表示如果存儲過程已經存在,則用新的存儲過程覆蓋,通常用於存儲過程的重建。
參數部分用於定義多個參數(如果沒有參數,就可以省略)。參數有三種形式:IN、OUT和IN OUT。如果沒有指明參數的形式,則默認爲IN。
關鍵字AS也可以寫成IS,後跟過程的說明部分,可以在此定義過程的局部變量。
編寫存儲過程可以使用任何文本編輯器或直接在SQL*Plus環境下進行,編寫好的存儲過程必須要在SQL*Plus環境下進行編譯,生成編譯代碼,原代碼和編譯代碼在編譯過程中都會被存入數據庫。編譯成功的存儲過程就可以在Oracle環境下進行調用了。
一個存儲過程在不需要時可以刪除。刪除存儲過程的人是過程的創建者或者擁有DROP ANY PROCEDURE系統權限的人。刪除存儲過程的語法如下:
DROP PROCEDURE 存儲過程名;
如果要重新編譯一個存儲過程,則只能是過程的創建者或者擁有ALTER ANY PROCEDURE系統權限的人。語法如下:
ALTER PROCEDURE 存儲過程名 COMPILE;
執行(或調用)存儲過程的人是過程的創建者或是擁有EXECUTE ANY PROCEDURE系統權限的人或是被擁有者授予EXECUTE權限的人。執行的方法如下:
方法1:
EXECUTE 模式名.存儲過程名[(參數…)];
方法2:
BEGIN
模式名.存儲過程名[(參數…)];
END;
傳遞的參數必須與定義的參數類型、個數和順序一致(如果參數定義了默認值,則調用時可以省略參數)。參數可以是變量、常量或表達式,用法參見下一節。
如果是調用本賬戶下的存儲過程,則模式名可以省略。要調用其他賬戶編寫的存儲過程,則模式名必須要添加。
以下是一個生成和調用簡單存儲過程的訓練。注意要事先授予創建存儲過程的權限。
【訓練1】 創建一個顯示僱員總人數的存儲過程。
步驟1:登錄SCOTT賬戶(或學生個人賬戶)。
步驟2:在SQL*Plus輸入區中,輸入以下存儲過程:
- CREATE OR REPLACE PROCEDURE EMP_COUNT
- AS
- V_TOTAL NUMBER(10);
- BEGIN
- SELECT COUNT(*) INTO V_TOTAL FROM EMP;
- DBMS_OUTPUT.PUT_LINE(’僱員總人數爲:’||V_TOTAL);
- END;
- CREATE OR REPLACE PROCEDURE EMP_COUNT
- AS
- V_TOTAL NUMBER(10);
- BEGIN
- SELECT COUNT(*) INTO V_TOTAL FROM EMP;
- DBMS_OUTPUT.PUT_LINE(’僱員總人數爲:’||V_TOTAL);
- END;
步驟3:按“執行”按鈕進行編譯。
如果存在錯誤,就會顯示:
警告: 創建的過程帶有編譯錯誤。
如果存在錯誤,對腳本進行修改,直到沒有錯誤產生。
如果編譯結果正確,將顯示:
- 過程已創建。
- 過程已創建。
步驟4:調用存儲過程,在輸入區中輸入以下語句並執行:
- EXECUTE EMP_COUNT;
- EXECUTE EMP_COUNT;
顯示結果爲:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
說明:在該訓練中,V_TOTAL變量是存儲過程定義的局部變量,用於接收查詢到的僱員總人數。
注意:在SQL*Plus中輸入存儲過程,按“執行”按鈕是進行編譯,不是執行存儲過程。
如果在存儲過程中引用了其他用戶的對象,比如表,則必須有其他用戶授予的對象訪問權限。一個存儲過程一旦編譯成功,就可以由其他用戶或程序來引用。但存儲過程或函數的所有者必須授予其他用戶執行該過程的權限。
存儲過程沒有參數,在調用時,直接寫過程名即可。
【訓練2】 在PL/SQL程序中調用存儲過程。
步驟1:登錄SCOTT賬戶。
步驟2:授權STUDENT賬戶使用該存儲過程,即在SQL*Plus輸入區中,輸入以下的命令:
- GRANT EXECUTE ON EMP_COUNT TO STUDENT
- GRANT EXECUTE ON EMP_COUNT TO STUDENT
- 授權成功。
- 授權成功。
步驟3:登錄STUDENT賬戶,在SQL*Plus輸入區中輸入以下程序:
- SET SERVEROUTPUT ON
- BEGIN
- SCOTT.EMP_COUNT;
- END;
- SET SERVEROUTPUT ON
- BEGIN
- SCOTT.EMP_COUNT;
- END;
步驟4:執行以上程序,結果爲:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
說明:在本例中,存儲過程是由SCOTT賬戶創建的,STUDEN賬戶獲得SCOTT賬戶的授權後,才能調用該存儲過程。
注意:在程序中調用存儲過程,使用了第二種語法。
【訓練3】 編寫顯示僱員信息的存儲過程EMP_LIST,並引用EMP_COUNT存儲過程。
步驟1:在SQL*Plus輸入區中輸入並編譯以下存儲過程:
- CREATE OR REPLACE PROCEDURE EMP_LIST
- AS
- CURSOR emp_cursor IS
- SELECT empno,ename FROM emp;
- BEGIN
- FOR Emp_record IN emp_cursor LOOP
- DBMS_OUTPUT.PUT_LINE(Emp_record.empno||Emp_record.ename);
- END LOOP;
- EMP_COUNT;
- END;
- CREATE OR REPLACE PROCEDURE EMP_LIST
- AS
- CURSOR emp_cursor IS
- SELECT empno,ename FROM emp;
- BEGIN
- FOR Emp_record IN emp_cursor LOOP
- DBMS_OUTPUT.PUT_LINE(Emp_record.empno||Emp_record.ename);
- END LOOP;
- EMP_COUNT;
- END;
執行結果:
- 過程已創建。
- 過程已創建。
步驟2:調用存儲過程,在輸入區中輸入以下語句並執行:
- EXECUTE EMP_LIST
- EXECUTE EMP_LIST
顯示結果爲:
- 7369SMITH
- 7499ALLEN
- 7521WARD
- 7566JONES
- 執行結果:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
- 7369SMITH
- 7499ALLEN
- 7521WARD
- 7566JONES
- 執行結果:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
說明:以上的EMP_LIST存儲過程中定義並使用了遊標,用來循環顯示所有僱員的信息。然後調用已經成功編譯的存儲過程EMP_COUNT,用來附加顯示僱員總人數。通過EXECUTE命令來執行EMP_LIST存儲過程。
【練習1】編寫顯示部門信息的存儲過程DEPT_LIST,要求統計出部門個數。
參數傳遞
參數的作用是向存儲過程傳遞數據,或從存儲過程獲得返回結果。正確的使用參數可以大大增加存儲過程的靈活性和通用性。
參數的類型有三種,如下所示。
- IN 定義一個輸入參數變量,用於傳遞參數給存儲過程
- OUT 定義一個輸出參數變量,用於從存儲過程獲取數據
- IN OUT 定義一個輸入、輸出參數變量,兼有以上兩者的功能
- IN 定義一個輸入參數變量,用於傳遞參數給存儲過程
- OUT 定義一個輸出參數變量,用於從存儲過程獲取數據
- IN OUT 定義一個輸入、輸出參數變量,兼有以上兩者的功能
參數的定義形式和作用如下:
參數名 IN 數據類型 DEFAULT 值;
定義一個輸入參數變量,用於傳遞參數給存儲過程。在調用存儲過程時,主程序的實際參數可以是常量、有值變量或表達式等。DEFAULT 關鍵字爲可選項,用來設定參數的默認值。如果在調用存儲過程時不指明參數,則參數變量取默認值。在存儲過程中,輸入變量接收主程序傳遞的值,但不能對其進行賦值。
參數名 OUT 數據類型;
定義一個輸出參數變量,用於從存儲過程獲取數據,即變量從存儲過程中返回值給主程序。
在調用存儲過程時,主程序的實際參數只能是一個變量,而不能是常量或表達式。在存儲過程中,參數變量只能被賦值而不能將其用於賦值,在存儲過程中必須給輸出變量至少賦值一次。
參數名 IN OUT 數據類型 DEFAULT 值;
定義一個輸入、輸出參數變量,兼有以上兩者的功能。在調用存儲過程時,主程序的實際參數只能是一個變量,而不能是常量或表達式。DEFAULT 關鍵字爲可選項,用來設定參數的默認值。在存儲過程中,變量接收主程序傳遞的值,同時可以參加賦值運算,也可以對其進行賦值。在存儲過程中必須給變量至少賦值一次。
如果省略IN、OUT或IN OUT,則默認模式是IN。
【訓練1】 編寫給僱員增加工資的存儲過程CHANGE_SALARY,通過IN類型的參數傳遞要增加工資的僱員編號和增加的工資額。
步驟1:登錄SCOTT賬戶。
步驟2:在SQL*Plus輸入區中輸入以下存儲過程並執行:
- CREATE OR REPLACE PROCEDURE CHANGE_SALARY(P_EMPNO IN NUMBER DEFAULT 7788,P_RAISE NUMBER DEFAULT 10)
- AS
- V_ENAME VARCHAR2(10);
- V_SAL NUMBER(5);
- BEGIN
- SELECT ENAME,SAL INTO V_ENAME,V_SAL FROM EMP WHERE EMPNO=P_EMPNO;
- UPDATE EMP SET SAL=SAL+P_RAISE WHERE EMPNO=P_EMPNO;
- DBMS_OUTPUT.PUT_LINE(’僱員’||V_ENAME||’的工資被改爲’||TO_CHAR(V_SAL+P_RAISE));
- COMMIT;
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’發生錯誤,修改失敗!’);
- ROLLBACK;
- END;
- CREATE OR REPLACE PROCEDURE CHANGE_SALARY(P_EMPNO IN NUMBER DEFAULT 7788,P_RAISE NUMBER DEFAULT 10)
- AS
- V_ENAME VARCHAR2(10);
- V_SAL NUMBER(5);
- BEGIN
- SELECT ENAME,SAL INTO V_ENAME,V_SAL FROM EMP WHERE EMPNO=P_EMPNO;
- UPDATE EMP SET SAL=SAL+P_RAISE WHERE EMPNO=P_EMPNO;
- DBMS_OUTPUT.PUT_LINE(’僱員’||V_ENAME||‘的工資被改爲’||TO_CHAR(V_SAL+P_RAISE));
- COMMIT;
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’發生錯誤,修改失敗!’);
- ROLLBACK;
- END;
執行結果爲:
- 過程已創建。
- 過程已創建。
步驟3:調用存儲過程,在輸入區中輸入以下語句並執行:
- EXECUTE CHANGE_SALARY(7788,80)
- EXECUTE CHANGE_SALARY(7788,80)
顯示結果爲:
- 僱員SCOTT的工資被改爲3080
- 僱員SCOTT的工資被改爲3080
說明:從執行結果可以看到,僱員SCOTT的工資已由原來的3000改爲3080。
參數的值由調用者傳遞,傳遞的參數的個數、類型和順序應該和定義的一致。如果順序不一致,可以採用以下調用方法。如上例,執行語句可以改爲:
EXECUTE CHANGE_SALARY(P_RAISE=>80,P_EMPNO=>7788);
可以看出傳遞參數的順序發生了變化,並且明確指出了參數名和要傳遞的值,=>運算符左側是參數名,右側是參數表達式,這種賦值方法的意義較清楚。
【練習1】創建插入僱員的存儲過程INSERT_EMP,並將僱員編號等作爲參數。
在設計存儲過程的時候,也可以爲參數設定默認值,這樣調用者就可以不傳遞或少傳遞參數了。
【訓練2】 調用存儲過程CHANGE_SALARY,不傳遞參數,使用默認參數值。
在SQL*Plus輸入區中輸入以下命令並執行:
- EXECUTE CHANGE_SALARY
- EXECUTE CHANGE_SALARY
顯示結果爲:
- 僱員SCOTT的工資被改爲3090
- 僱員SCOTT的工資被改爲3090
說明:在存儲過程的調用中沒有傳遞參數,而是採用了默認值7788和10,即默認僱員號爲7788,增加的工資爲10。
【訓練3】 使用OUT類型的參數返回存儲過程的結果。
步驟1:登錄SCOTT賬戶。
步驟2:在SQL*Plus輸入區中輸入並編譯以下存儲過程:
- CREATE OR REPLACE PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)
- AS
- BEGIN
- SELECT COUNT(*) INTO P_TOTAL FROM EMP;
- END;
- CREATE OR REPLACE PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)
- AS
- BEGIN
- SELECT COUNT(*) INTO P_TOTAL FROM EMP;
- END;
執行結果爲:
- 過程已創建。
- 過程已創建。
步驟3:輸入以下程序並執行:
- DECLARE
- V_EMPCOUNT NUMBER;
- BEGIN
- EMP_COUNT(V_EMPCOUNT);
- DBMS_OUTPUT.PUT_LINE(’僱員總人數爲:’||V_EMPCOUNT);
- END;
- DECLARE
- V_EMPCOUNT NUMBER;
- BEGIN
- EMP_COUNT(V_EMPCOUNT);
- DBMS_OUTPUT.PUT_LINE(’僱員總人數爲:’||V_EMPCOUNT);
- END;
顯示結果爲:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
說明:在存儲過程中定義了OUT類型的參數P_TOTAL,在主程序調用該存儲過程時,傳遞了參數V_EMPCOUNT。在存儲過程中的SELECT…INTO…語句中對P_TOTAL進行賦值,賦值結果由V_EMPCOUNT變量帶回給主程序並顯示。
以上程序要覆蓋同名的EMP_COUNT存儲過程,如果不使用OR REPLACE選項,就會出現以下錯誤:
- ERROR 位於第 1 行:
- ORA-00955: 名稱已由現有對象使用。
- ERROR 位於第 1 行:
- ORA-00955: 名稱已由現有對象使用。
【練習2】創建存儲過程,使用OUT類型參數獲得僱員經理名。
【訓練4】 使用IN OUT類型的參數,給電話號碼增加區碼。
步驟1:登錄SCOTT賬戶。
步驟2:在SQL*Plus輸入區中輸入並編譯以下存儲過程:
- CREATE OR REPLACE PROCEDURE ADD_REGION(P_HPONE_NUM IN OUT VARCHAR2)
- AS
- BEGIN
- P_HPONE_NUM:=’0755-‘||P_HPONE_NUM;
- END;
- CREATE OR REPLACE PROCEDURE ADD_REGION(P_HPONE_NUM IN OUT VARCHAR2)
- AS
- BEGIN
- P_HPONE_NUM:=’0755-‘||P_HPONE_NUM;
- END;
執行結果爲:
- 過程已創建。
- 過程已創建。
步驟3:輸入以下程序並執行:
- SET SERVEROUTPUT ON
- DECLARE
- V_PHONE_NUM VARCHAR2(15);
- BEGIN
- V_PHONE_NUM:=’26731092’;
- ADD_REGION(V_PHONE_NUM);
- DBMS_OUTPUT.PUT_LINE(’新的電話號碼:’||V_PHONE_NUM);
- END;
- SET SERVEROUTPUT ON
- DECLARE
- V_PHONE_NUM VARCHAR2(15);
- BEGIN
- V_PHONE_NUM:=’26731092’;
- ADD_REGION(V_PHONE_NUM);
- DBMS_OUTPUT.PUT_LINE(’新的電話號碼:’||V_PHONE_NUM);
- END;
顯示結果爲:
- 新的電話號碼:0755-26731092
- PL/SQL 過程已成功完成。
- 新的電話號碼:0755-26731092
- PL/SQL 過程已成功完成。
說明:變量V_HPONE_NUM既用來向存儲過程傳遞舊電話號碼,也用來向主程序返回新號碼。新的號碼在原來基礎上增加了區號0755和-。
創建和刪除存儲函數
創建函數,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系統權限。該權限可由系統管理員授予。創建存儲函數的語法和創建存儲過程的類似,即
CREATE [OR REPLACE] FUNCTION 函數名[(參數[IN] 數據類型…)]
RETURN 數據類型
{AS|IS}
[說明部分]
BEGIN
可執行部分
RETURN (表達式)
[EXCEPTION
錯誤處理部分]
END [函數名];
其中,參數是可選的,但只能是IN類型(IN關鍵字可以省略)。
在定義部分的RETURN 數據類型,用來表示函數的數據類型,也就是返回值的類型,此部分不可省略。
在可執行部分的RETURN(表達式),用來生成函數的返回值,其表達式的類型應該和定義部分說明的函數返回值的數據類型一致。在函數的執行部分可以有多個RETURN語句,但只有一個RETURN語句會被執行,一旦執行了RETURN語句,則函數結束並返回調用環境。
一個存儲函數在不需要時可以刪除,但刪除的人應是函數的創建者或者是擁有DROP ANY PROCEDURE系統權限的人。其語法如下:
DROP FUNCTION 函數名;
重新編譯一個存儲函數時,編譯的人應是函數的創建者或者擁有ALTER ANY PROCEDURE系統權限的人。重新編譯一個存儲函數的語法如下:
ALTER PROCEDURE 函數名 COMPILE;
函數的調用者應是函數的創建者或擁有EXECUTE ANY PROCEDURE系統權限的人,或是被函數的擁有者授予了函數執行權限的賬戶。函數的引用和存儲過程不同,函數要出現在程序體中,可以參加表達式的運算或單獨出現在表達式中,其形式如下:
變量名:=函數名(…)
【訓練1】 創建一個通過僱員編號返回僱員名稱的函數GET_EMP_NAME。
步驟1:登錄SCOTT賬戶。
步驟2:在SQL*Plus輸入區中輸入以下存儲函數並編譯:
- CREATE OR REPLACE FUNCTION GET_EMP_NAME(P_EMPNO NUMBER DEFAULT 7788)
- RETURN VARCHAR2
- AS
- V_ENAME VARCHAR2(10);
- BEGIN
- ELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO=P_EMPNO;
- RETURN(V_ENAME);
- EXCEPTION
- WHEN NO_DATA_FOUND THEN
- DBMS_OUTPUT.PUT_LINE(’沒有該編號僱員!’);
- RETURN (NULL);
- WHEN TOO_MANY_ROWS THEN
- DBMS_OUTPUT.PUT_LINE(’有重複僱員編號!’);
- RETURN (NULL);
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’發生其他錯誤!’);
- RETURN (NULL);
- END;
- CREATE OR REPLACE FUNCTION GET_EMP_NAME(P_EMPNO NUMBER DEFAULT 7788)
- RETURN VARCHAR2
- AS
- V_ENAME VARCHAR2(10);
- BEGIN
- ELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO=P_EMPNO;
- RETURN(V_ENAME);
- EXCEPTION
- WHEN NO_DATA_FOUND THEN
- DBMS_OUTPUT.PUT_LINE(’沒有該編號僱員!’);
- RETURN (NULL);
- WHEN TOO_MANY_ROWS THEN
- DBMS_OUTPUT.PUT_LINE(’有重複僱員編號!’);
- RETURN (NULL);
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’發生其他錯誤!’);
- RETURN (NULL);
- END;
步驟3:調用該存儲函數,輸入並執行以下程序:
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’僱員7369的名稱是:’|| GET_EMP_NAME(7369));
- DBMS_OUTPUT.PUT_LINE(’僱員7839的名稱是:’|| GET_EMP_NAME(7839));
- END;
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’僱員7369的名稱是:’|| GET_EMP_NAME(7369));
- DBMS_OUTPUT.PUT_LINE(’僱員7839的名稱是:’|| GET_EMP_NAME(7839));
- END;
顯示結果爲:
- 僱員7369的名稱是:SMITH
- 僱員7839的名稱是:KING
- PL/SQL 過程已成功完成。
- 僱員7369的名稱是:SMITH
- 僱員7839的名稱是:KING
- PL/SQL 過程已成功完成。
說明:函數的調用直接出現在程序的DBMS_OUTPUT.PUT_LINE語句中,作爲字符串表達式的一部分。如果輸入了錯誤的僱員編號,就會在函數的錯誤處理部分輸出錯誤信息。試修改僱員編號,重新運行調用部分。
【練習1】創建一個通過部門編號返回部門名稱的存儲函數GET_DEPT_NAME。
【練習2】將函數的執行權限授予STUDENT賬戶,然後登錄STUDENT賬戶調用。
存儲過程和函數的查看
可以通過對數據字典的訪問來查詢存儲過程或函數的有關信息,如果要查詢當前用戶的存儲過程或函數的源代碼,可以通過對USER_SOURCE數據字典視圖的查詢得到。USER_SOURCE的結構如下:
- DESCRIBE USER_SOURCE
- DESCRIBE USER_SOURCE
結果爲:
- 名稱 是否爲空? 類型
- ————————————————————- ————- ———————–
- NAME VARCHAR2(30)
- TYPE VARCHAR2(12)
- LINE NUMBER
- TEXT VARCHAR2(4000)
- 名稱 是否爲空? 類型
- ————————————————————- ————- ———————–
- NAME VARCHAR2(30)
- TYPE VARCHAR2(12)
- LINE NUMBER
- TEXT VARCHAR2(4000)
說明:裏面按行存放着過程或函數的腳本,NAME是過程或函數名,TYPE 代表類型(PROCEDURE或FUNCTION),LINE是行號,TEXT 爲腳本。
【訓練1】 查詢過程EMP_COUNT的腳本。
在SQL*Plus中輸入並執行如下查詢:
- select TEXT from user_source WHERE NAME=’EMP_COUNT’;
- select TEXT from user_source WHERE NAME=‘EMP_COUNT’;
結果爲:
- TEXT
- ——————————————————————————–
- PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)
- AS
- BEGIN
- SELECT COUNT(*) INTO P_TOTAL FROM EMP;
- END;
- TEXT
- ——————————————————————————–
- PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)
- AS
- BEGIN
- SELECT COUNT(*) INTO P_TOTAL FROM EMP;
- END;
【訓練2】 查詢過程GET_EMP_NAME的參數。
在SQL*Plus中輸入並執行如下查詢:
- DESCRIBE GET_EMP_NAME
- DESCRIBE GET_EMP_NAME
結果爲:
- FUNCTION GET_EMP_NAME RETURNS VARCHAR2
- 參數名稱 類型 輸入/輸出默認值?
- —————————————– ———————————– —————– ————-
- P_EMPNO NUMBER(4) IN DEFAULT
- FUNCTION GET_EMP_NAME RETURNS VARCHAR2
- 參數名稱 類型 輸入/輸出默認值?
- —————————————– ———————————– —————– ————-
- P_EMPNO NUMBER(4) IN DEFAULT
【訓練3】 在發生編譯錯誤時,顯示錯誤。
- SHOW ERRORS
- SHOW ERRORS
以下是一段編譯錯誤顯示:
- LINE/COL ERROR
- ————- —————————————————————–
- 4/2 PL/SQL: SQL Statement ignored
- 4/36 PLS-00201: 必須說明標識符 ’EMPP’
- LINE/COL ERROR
- ————- —————————————————————–
- 4/2 PL/SQL: SQL Statement ignored
- 4/36 PLS-00201: 必須說明標識符 ’EMPP’
說明:查詢一個存儲過程或函數是否是有效狀態(即編譯成功),可以使用數據字典USER_OBJECTS的STATUS列。
【訓練4】 查詢EMP_LIST存儲過程是否可用:
- SELECT STATUS FROM USER_OBJECTS WHERE OBJECT_NAME=’EMP_LIST’;
- SELECT STATUS FROM USER_OBJECTS WHERE OBJECT_NAME=‘EMP_LIST’;
結果爲:
- STATUS
- ————
- VALID
- STATUS
- ————
- VALID
說明:VALID表示該存儲過程有效(即通過編譯),INVALID表示存儲過程無效或需要重新編譯。當Oracle調用一個無效的存儲過程或函數時,首先試圖對其進行編譯,如果編譯成功則將狀態置成VALID並執行,否則給出錯誤信息。
當一個存儲過程編譯成功,狀態變爲VALID,會不會在某些情況下變成INVALID。結論是完全可能的。比如一個存儲過程中包含對錶的查詢,如果表被修改或刪除,存儲過程就會變成無效INVALID。所以要注意存儲過程和函數對其他對象的依賴關係。
如果要檢查存儲過程或函數的依賴性,可以通過查詢數據字典USER_DENPENDENCIES來確定,該表結構如下:
- DESCRIBE USER_DEPENDENCIES;
- DESCRIBE USER_DEPENDENCIES;
結果:
- 名稱 是否爲空? 類型
- ————————————————————– ————- —————————-
- NAME NOT NULL VARCHAR2(30)
- TYPE VARCHAR2(12)
- REFERENCED_OWNER VARCHAR2(30)
- REFERENCED_NAME VARCHAR2(64)
- REFERENCED_TYPE VARCHAR2(12)
- REFERENCED_LINK_NAME VARCHAR2(128)
- SCHEMAID NUMBER
- DEPENDENCY_TYPE VARCHAR2(4)
- 名稱 是否爲空? 類型
- ————————————————————– ————- —————————-
- NAME NOT NULL VARCHAR2(30)
- TYPE VARCHAR2(12)
- REFERENCED_OWNER VARCHAR2(30)
- REFERENCED_NAME VARCHAR2(64)
- REFERENCED_TYPE VARCHAR2(12)
- REFERENCED_LINK_NAME VARCHAR2(128)
- SCHEMAID NUMBER
- DEPENDENCY_TYPE VARCHAR2(4)
說明:NAME爲實體名,TYPE爲實體類型,REFERENCED_OWNER爲涉及到的實體擁有者賬戶,REFERENCED_NAME爲涉及到的實體名,REFERENCED_TYPE 爲涉及到的實體類型。
【訓練5】 查詢EMP_LIST存儲過程的依賴性。
- SELECT REFERENCED_NAME,REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE NAME=’EMP_LIST’;
- SELECT REFERENCED_NAME,REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE NAME=‘EMP_LIST’;
執行結果:
- REFERENCED_NAME REFERENCED_TYPE
- —————————————————————————————— —————————-
- STANDARD PACKAGE
- SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
- DBMS_OUTPUT PACKAGE
- DBMS_OUTPUT SYNONYM
- DBMS_OUTPUT NON-EXISTENT
- EMP TABLE
- EMP_COUNT PROCEDURE
- REFERENCED_NAME REFERENCED_TYPE
- —————————————————————————————— —————————-
- STANDARD PACKAGE
- SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
- DBMS_OUTPUT PACKAGE
- DBMS_OUTPUT SYNONYM
- DBMS_OUTPUT NON-EXISTENT
- EMP TABLE
- EMP_COUNT PROCEDURE
說明:可以看出存儲過程EMP_LIST依賴一些系統包、EMP表和EMP_COUNT存儲過程。如果刪除了EMP表或EMP_COUNT存儲過程,EMP_LIST將變成無效。
還有一種情況需要我們注意:如果一個用戶A被授予執行屬於用戶B的一個存儲過程的權限,在用戶B的存儲過程中,訪問到用戶C的表,用戶B被授予訪問用戶C的表的權限,但用戶A沒有被授予訪問用戶C表的權限,那麼用戶A調用用戶B的存儲過程是失敗的還是成功的呢?答案是成功的。如果讀者有興趣,不妨進行一下實際測試。
包
包的概念和組成
包是用來存儲相關程序結構的對象,它存儲於數據字典中。包由兩個分離的部分組成:包頭(PACKAGE)和包體(PACKAGE BODY)。包頭是包的說明部分,是對外的操作接口,對應用是可見的;包體是包的代碼和實現部分,對應用來說是不可見的黑盒。
包中可以包含的程序結構如下所示。
- 過程(PROCUDURE) 帶參數的命名的程序模塊
- 函數(FUNCTION) 帶參數、具有返回值的命名的程序模塊
- 變量(VARIABLE) 存儲變化的量的存儲單元
- 常量(CONSTANT) 存儲不變的量的存儲單元
- 遊標(CURSOR) 用戶定義的數據操作緩存區,在可執行部分使用
- 類型(TYPE) 用戶定義的新的結構類型
- 異常(EXCEPTION) 在標準包中定義或由用戶自定義,用於處理程序錯誤
- 過程(PROCUDURE) 帶參數的命名的程序模塊
- 函數(FUNCTION) 帶參數、具有返回值的命名的程序模塊
- 變量(VARIABLE) 存儲變化的量的存儲單元
- 常量(CONSTANT) 存儲不變的量的存儲單元
- 遊標(CURSOR) 用戶定義的數據操作緩存區,在可執行部分使用
- 類型(TYPE) 用戶定義的新的結構類型
- 異常(EXCEPTION) 在標準包中定義或由用戶自定義,用於處理程序錯誤
說明部分可以出現在包的三個不同的部分:出現在包頭中的稱爲公有元素,出現在包體中的稱爲私有元素,出現在包體的過程(或函數)中的稱爲局部變量。它們的性質有所不同,如下所示。
- 公有元素(PUBLIC) 在包頭中說明,在包體中具體定義 在包外可見並可以訪問,對整個應用的全過程有效
- 私有元素(PRIVATE) 在包體的說明部分說明 只能被包內部的其他部分訪問
- 局部變量(LOCAL) 在過程或函數的說明部分說明 只能在定義變量的過程或函數中使用
- 公有元素(PUBLIC) 在包頭中說明,在包體中具體定義 在包外可見並可以訪問,對整個應用的全過程有效
- 私有元素(PRIVATE) 在包體的說明部分說明 只能被包內部的其他部分訪問
- 局部變量(LOCAL) 在過程或函數的說明部分說明 只能在定義變量的過程或函數中使用
在包體中出現的過程或函數,如果需要對外公用,就必須在包頭中說明,包頭中的說明應該和包體中的說明一致。
包有以下優點:
* 包可以方便地將存儲過程和函數組織到一起,每個包又是相互獨立的。在不同的包中,過程、函數都可以重名,這解決了在同一個用戶環境中命名的衝突問題。
* 包增強了對存儲過程和函數的安全管理,對整個包的訪問權只需一次授予。
* 在同一個會話中,公用變量的值將被保留,直到會話結束。
* 區分了公有過程和私有過程,包體的私有過程增加了過程和函數的保密性。
* 包在被首次調用時,就作爲一個整體被全部調入內存,減少了多次訪問過程或函數的I/O次數。
創建包和包體
包由包頭和包體兩部分組成,包的創建應該先創建包頭部分,然後創建包體部分。創建、刪除和編譯包的權限同創建、刪除和編譯存儲過程的權限相同。
創建包頭的簡要語句如下:
CREATE [OR REPLACE] PACKAGE 包名
{IS|AS}
公有變量定義
公有類型定義
公有遊標定義
公有異常定義
函數說明
過程說明
END;
創建包體的簡要語法如下:
CREATE [OR REPLACE] PACKAGE BODY 包名
{IS|AS}
私有變量定義
私有類型定義
私有遊標定義
私有異常定義
函數定義
過程定義
END;
包的其他操作命令包括:
刪除包頭:
DROP PACKAGE 包頭名
刪除包體:
DROP PACKAGE BODY 包體名
重新編譯包頭:
ALTER PACKAGE 包名 COMPILE PACKAGE
重新編譯包體:
ALTER PACKAGE 包名 COMPILE PACKAGE BODY
在包頭中說明的對象可以在包外調用,調用的方法和調用單獨的過程或函數的方法基本相同,惟一的區別就是要在調用的過程或函數名前加上包的名字(中間用“.”分隔)。但要注意,不同的會話將單獨對包的公用變量進行初始化,所以不同的會話對包的調用屬於不同的應用。
系統包
Oracle預定義了很多標準的系統包,這些包可以在應用中直接使用,比如在訓練中我們使用的DBMS_OUTPUT包,就是系統包。PUT_LINE是該包的一個函數。常用系統包下所示。
- DBMS_OUTPUT 在SQL*Plus環境下輸出信息
- DBMS_DDL 編譯過程函數和包
- DBMS_SESSION 改變用戶的會話,初始化包等
- DBMS_TRANSACTION 控制數據庫事務
- DBMS_MAIL 連接Oracle*Mail
- DBMS_LOCK 進行復雜的鎖機制管理
- DBMS_ALERT 識別數據庫事件告警
- DBMS_PIPE 通過管道在會話間傳遞信息
- DBMS_JOB 管理Oracle的作業
- DBMS_LOB 操縱大對象
- DBMS_SQL 執行動態SQL語句
- DBMS_OUTPUT 在SQL*Plus環境下輸出信息
- DBMS_DDL 編譯過程函數和包
- DBMS_SESSION 改變用戶的會話,初始化包等
- DBMS_TRANSACTION 控制數據庫事務
- DBMS_MAIL 連接Oracle*Mail
- DBMS_LOCK 進行復雜的鎖機制管理
- DBMS_ALERT 識別數據庫事件告警
- DBMS_PIPE 通過管道在會話間傳遞信息
- DBMS_JOB 管理Oracle的作業
- DBMS_LOB 操縱大對象
- DBMS_SQL 執行動態SQL語句
包的應用
在SQL*Plus環境下,包和包體可以分別編譯,也可以一起編譯。如果分別編譯,則要先編譯包頭,後編譯包體。如果在一起編譯,則包頭寫在前,包體在後,中間用“/”分隔。
可以將已經存在的存儲過程或函數添加到包中,方法是去掉過程或函數創建語句的CREATE OR REPLACE部分,將存儲過程或函數複製到包體中 ,然後重新編譯即可。
如果需要將私有過程或函數變成共有過程或函數的話,將過程或函數說明部分複製到包頭說明部分,然後重新編譯就可以了。
【訓練1】 創建管理僱員信息的包EMPLOYE,它具有從EMP表獲得僱員信息,修改僱員名稱,修改僱員工資和寫回EMP表的功能。
步驟1:登錄SCOTT賬戶,輸入以下代碼並編譯:
- CREATE OR REPLACE PACKAGE EMPLOYE –包頭部分
- IS
- PROCEDURE SHOW_DETAIL;
- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER);
- PROCEDURE SAVE_EMPLOYE;
- PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2);
- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER);
- END EMPLOYE;
- /
- CREATE OR REPLACE PACKAGE BODY EMPLOYE –包體部分
- IS
- EMPLOYE EMP%ROWTYPE;
- ————– 顯示僱員信息 —————
- PROCEDURE SHOW_DETAIL
- AS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(‘—– 僱員信息 —–’);
- DBMS_OUTPUT.PUT_LINE(’僱員編號:’||EMPLOYE.EMPNO);
- DBMS_OUTPUT.PUT_LINE(’僱員名稱:’||EMPLOYE.ENAME);
- DBMS_OUTPUT.PUT_LINE(’僱員職務:’||EMPLOYE.JOB);
- DBMS_OUTPUT.PUT_LINE(’僱員工資:’||EMPLOYE.SAL);
- DBMS_OUTPUT.PUT_LINE(’部門編號:’||EMPLOYE.DEPTNO);
- END SHOW_DETAIL;
- —————– 從EMP表取得一個僱員 ——————–
- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER)
- AS
- BEGIN
- SELECT * INTO EMPLOYE FROM EMP WHERE EMPNO=P_EMPNO;
- DBMS_OUTPUT.PUT_LINE(’獲取僱員’||EMPLOYE.ENAME||’信息成功’);
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’獲取僱員信息發生錯誤!’);
- END GET_EMPLOYE;
- ———————- 保存僱員到EMP表 ————————–
- PROCEDURE SAVE_EMPLOYE
- AS
- BEGIN
- UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=
- EMPLOYE.EMPNO;
- DBMS_OUTPUT.PUT_LINE(’僱員信息保存完成!’);
- END SAVE_EMPLOYE;
- —————————- 修改僱員名稱 ——————————
- PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2)
- AS
- BEGIN
- EMPLOYE.ENAME:=P_NEWNAME;
- DBMS_OUTPUT.PUT_LINE(’修改名稱完成!’);
- END CHANGE_NAME;
- —————————- 修改僱員工資 ————————–
- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER)
- AS
- BEGIN
- EMPLOYE.SAL:=P_NEWSAL;
- DBMS_OUTPUT.PUT_LINE(’修改工資完成!’);
- END CHANGE_SAL;
- END EMPLOYE;
- CREATE OR REPLACE PACKAGE EMPLOYE –包頭部分
- IS
- PROCEDURE SHOW_DETAIL;
- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER);
- PROCEDURE SAVE_EMPLOYE;
- PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2);
- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER);
- END EMPLOYE;
- /
- CREATE OR REPLACE PACKAGE BODY EMPLOYE –包體部分
- IS
- EMPLOYE EMP%ROWTYPE;
- ————– 顯示僱員信息 —————
- PROCEDURE SHOW_DETAIL
- AS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(‘—– 僱員信息 —–’);
- DBMS_OUTPUT.PUT_LINE(’僱員編號:’||EMPLOYE.EMPNO);
- DBMS_OUTPUT.PUT_LINE(’僱員名稱:’||EMPLOYE.ENAME);
- DBMS_OUTPUT.PUT_LINE(’僱員職務:’||EMPLOYE.JOB);
- DBMS_OUTPUT.PUT_LINE(’僱員工資:’||EMPLOYE.SAL);
- DBMS_OUTPUT.PUT_LINE(’部門編號:’||EMPLOYE.DEPTNO);
- END SHOW_DETAIL;
- —————– 從EMP表取得一個僱員 ——————–
- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER)
- AS
- BEGIN
- SELECT * INTO EMPLOYE FROM EMP WHERE EMPNO=P_EMPNO;
- DBMS_OUTPUT.PUT_LINE(’獲取僱員’||EMPLOYE.ENAME||‘信息成功’);
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’獲取僱員信息發生錯誤!’);
- END GET_EMPLOYE;
- ———————- 保存僱員到EMP表 ————————–
- PROCEDURE SAVE_EMPLOYE
- AS
- BEGIN
- UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=
- EMPLOYE.EMPNO;
- DBMS_OUTPUT.PUT_LINE(’僱員信息保存完成!’);
- END SAVE_EMPLOYE;
- —————————- 修改僱員名稱 ——————————
- PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2)
- AS
- BEGIN
- EMPLOYE.ENAME:=P_NEWNAME;
- DBMS_OUTPUT.PUT_LINE(’修改名稱完成!’);
- END CHANGE_NAME;
- —————————- 修改僱員工資 ————————–
- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER)
- AS
- BEGIN
- EMPLOYE.SAL:=P_NEWSAL;
- DBMS_OUTPUT.PUT_LINE(’修改工資完成!’);
- END CHANGE_SAL;
- END EMPLOYE;
步驟2:獲取僱員7788的信息:
- SET SERVEROUTPUT ON
- EXECUTE EMPLOYE.GET_EMPLOYE(7788);
- SET SERVEROUTPUT ON
- EXECUTE EMPLOYE.GET_EMPLOYE(7788);
結果爲:
- 獲取僱員SCOTT信息成功
- PL/SQL 過程已成功完成。
- 獲取僱員SCOTT信息成功
- PL/SQL 過程已成功完成。
步驟3:顯示僱員信息:
- EXECUTE EMPLOYE.SHOW_DETAIL;
- EXECUTE EMPLOYE.SHOW_DETAIL;
結果爲:
- —————— 僱員信息 ——————
- 僱員編號:7788
- 僱員名稱:SCOTT
- 僱員職務:ANALYST
- 僱員工資:3000
- 部門編號:20
- PL/SQL 過程已成功完成。
- —————— 僱員信息 ——————
- 僱員編號:7788
- 僱員名稱:SCOTT
- 僱員職務:ANALYST
- 僱員工資:3000
- 部門編號:20
- PL/SQL 過程已成功完成。
步驟4:修改僱員工資:
- EXECUTE EMPLOYE.CHANGE_SAL(3800);
- EXECUTE EMPLOYE.CHANGE_SAL(3800);
結果爲:
- 修改工資完成!
- PL/SQL 過程已成功完成。
- 修改工資完成!
- PL/SQL 過程已成功完成。
步驟5:將修改的僱員信息存入EMP表
- EXECUTE EMPLOYE.SAVE_EMPLOYE;
- EXECUTE EMPLOYE.SAVE_EMPLOYE;
結果爲:
- 僱員信息保存完成!
- PL/SQL 過程已成功完成。
- 僱員信息保存完成!
- PL/SQL 過程已成功完成。
說明:該包完成將EMP表中的某個僱員的信息取入內存記錄變量,在記錄變量中進行修改編輯,在確認顯示信息正確後寫回EMP表的功能。記錄變量EMPLOYE用來存儲取得的僱員信息,定義爲私有變量,只能被包的內部模塊訪問。
【練習1】爲包增加修改僱員職務和部門編號的功能。
階段訓練
下面的訓練通過定義和創建完整的包EMP_PK並綜合運用本章的知識,完成對僱員表的插入、刪除等功能,包中的主要元素解釋如下所示。
- 程序結構 類 型 說 明
- V_EMP_COUNT 公有變量 跟蹤僱員的總人數變化,插入、刪除僱員的同時修改該變量的值
- INIT 公有過程 對包進行初始化,初始化僱員人數和工資修改的上、下限
- LIST_EMP 公有過程 顯示僱員列表
- INSERT_EMP 公有過程 通過編號插入新僱員
- DELETE_EMP 公有過程 通過編號刪除僱員
- CHANGE_EMP_SAL 公有過程 通過編號修改僱員工資
- V_MESSAGE 私有變量 存放準備輸出的信息
- C_MAX_SAL 私有變量 對工資修改的上限
- C_MIN_SAL 私有變量 對工資修改的下限
- SHOW_MESSAGE 私有過程 顯示私有變量V_MESSAGE中的信息
- EXIST_EMP 私有函數 判斷某個編號的僱員是否存在,該函數被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等過程調用
- 程序結構 類 型 說 明
- V_EMP_COUNT 公有變量 跟蹤僱員的總人數變化,插入、刪除僱員的同時修改該變量的值
- INIT 公有過程 對包進行初始化,初始化僱員人數和工資修改的上、下限
- LIST_EMP 公有過程 顯示僱員列表
- INSERT_EMP 公有過程 通過編號插入新僱員
- DELETE_EMP 公有過程 通過編號刪除僱員
- CHANGE_EMP_SAL 公有過程 通過編號修改僱員工資
- V_MESSAGE 私有變量 存放準備輸出的信息
- C_MAX_SAL 私有變量 對工資修改的上限
- C_MIN_SAL 私有變量 對工資修改的下限
- SHOW_MESSAGE 私有過程 顯示私有變量V_MESSAGE中的信息
- EXIST_EMP 私有函數 判斷某個編號的僱員是否存在,該函數被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等過程調用
【訓練1】 完整的僱員包EMP_PK的創建和應用。
步驟1:在SQL*Plus中登錄SCOTT賬戶,輸入以下包頭和包體部分,按“執行”按鈕編譯:
- CREATE OR REPLACE PACKAGE EMP_PK
- –包頭部分
- IS
- V_EMP_COUNT NUMBER(5);
- –僱員人數
- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER); –初始化
- PROCEDURE LIST_EMP;
- –顯示僱員列表
- PROCEDURE INSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,
- P_SAL NUMBER);
- –插入僱員
- PROCEDURE DELETE_EMP(P_EMPNO NUMBER); –刪除僱員
- PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER);
- –修改僱員工資
- END EMP_PK;
- /CREATE OR REPLACE PACKAGE BODY EMP_PK
- –包體部分
- IS
- V_MESSAGE VARCHAR2(50); –顯示信息
- V_MAX_SAL NUMBER(7); –工資上限
- V_MIN_SAL NUMBER(7); –工資下限
- FUNCTION EXIST_EMP(P_EMPNO NUMBER) RETURN BOOLEAN; –判斷僱員是否存在函數
- PROCEDURE SHOW_MESSAGE; –顯示信息過程
- ——————————- 初始化過程 —————————-
- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER)
- IS
- BEGIN
- SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP;
- V_MAX_SAL:=P_MAX;
- V_MIN_SAL:=P_MIN;
- V_MESSAGE:=’初始化過程已經完成!’;
- SHOW_MESSAGE;
- END INIT;
- —————————- 顯示僱員列表過程 ———————
- PROCEDURE LIST_EMP
- IS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’姓名 職務 工資’);
- FOR emp_rec IN (SELECT * FROM EMP)
- LOOP
- DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,”)||RPAD(emp_rec.job,10,’ ’)||TO_CHAR(emp_rec.sal));
- END LOOP;
- DBMS_OUTPUT.PUT_LINE(’僱員總人數’||V_EMP_COUNT);
- END LIST_EMP;
- —————————– 插入僱員過程 —————————–
- PROCEDUREINSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,P_SAL NUMBER)
- IS
- BEGIN
- IF NOT EXIST_EMP(P_EMPNO) THEN
- INSERT INTO EMP(EMPNO,ENAME,JOB,SAL) VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL);
- COMMIT;
- V_EMP_COUNT:=V_EMP_COUNT+1;
- V_MESSAGE:=’僱員’||P_EMPNO||’已插入!’;
- ELSE
- V_MESSAGE:=’僱員’||P_EMPNO||’已存在,不能插入!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||’插入失敗!’;
- SHOW_MESSAGE;
- END INSERT_EMP;
- ————————— 刪除僱員過程 ——————–
- PROCEDURE DELETE_EMP(P_EMPNO NUMBER)
- IS
- BEGIN
- IF EXIST_EMP(P_EMPNO) THEN
- DELETE FROM EMP WHERE EMPNO=P_EMPNO;
- COMMIT;
- V_EMP_COUNT:=V_EMP_COUNT-1;
- V_MESSAGE:=’僱員’||P_EMPNO||’已刪除!’;
- ELSE
- V_MESSAGE:=’僱員’||P_EMPNO||’不存在,不能刪除!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||’刪除失敗!’;
- SHOW_MESSAGE;
- END DELETE_EMP;
- ————————————— 修改僱員工資過程 ————————————
- PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER)
- IS
- BEGIN
- IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN
- V_MESSAGE:=’工資超出修改範圍!’;
- ELSIF NOT EXIST_EMP(P_EMPNO) THEN
- V_MESSAGE:=’僱員’||P_EMPNO||’不存在,不能修改工資!’;
- ELSE
- UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO;
- COMMIT;
- V_MESSAGE:=’僱員’||P_EMPNO||’工資已經修改!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||’工資修改失敗!’;
- SHOW_MESSAGE;
- END CHANGE_EMP_SAL;
- —————————- 顯示信息過程 —————————-
- PROCEDURE SHOW_MESSAGE
- IS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’提示信息:’||V_MESSAGE);
- END SHOW_MESSAGE;
- ———————— 判斷僱員是否存在函數 ——————-
- FUNCTION EXIST_EMP(P_EMPNO NUMBER)
- RETURN BOOLEAN
- IS
- V_NUM NUMBER; –局部變量
- BEGIN
- SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO;
- IF V_NUM=1 THEN
- RETURN TRUE;
- ELSE
- RETURN FALSE;
- END IF;
- END EXIST_EMP;
- —————————–
- END EMP_PK;
- CREATE OR REPLACE PACKAGE EMP_PK
- –包頭部分
- IS
- V_EMP_COUNT NUMBER(5);
- –僱員人數
- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER); –初始化
- PROCEDURE LIST_EMP;
- –顯示僱員列表
- PROCEDURE INSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,
- P_SAL NUMBER);
- –插入僱員
- PROCEDURE DELETE_EMP(P_EMPNO NUMBER); –刪除僱員
- PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER);
- –修改僱員工資
- END EMP_PK;
- /CREATE OR REPLACE PACKAGE BODY EMP_PK
- –包體部分
- IS
- V_MESSAGE VARCHAR2(50); –顯示信息
- V_MAX_SAL NUMBER(7); –工資上限
- V_MIN_SAL NUMBER(7); –工資下限
- FUNCTION EXIST_EMP(P_EMPNO NUMBER) RETURN BOOLEAN; –判斷僱員是否存在函數
- PROCEDURE SHOW_MESSAGE; –顯示信息過程
- ——————————- 初始化過程 —————————-
- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER)
- IS
- BEGIN
- SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP;
- V_MAX_SAL:=P_MAX;
- V_MIN_SAL:=P_MIN;
- V_MESSAGE:=’初始化過程已經完成!’;
- SHOW_MESSAGE;
- END INIT;
- —————————- 顯示僱員列表過程 ———————
- PROCEDURE LIST_EMP
- IS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’姓名 職務 工資’);
- FOR emp_rec IN (SELECT * FROM EMP)
- LOOP
- DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,”)||RPAD(emp_rec.job,10,‘ ’)||TO_CHAR(emp_rec.sal));
- END LOOP;
- DBMS_OUTPUT.PUT_LINE(’僱員總人數’||V_EMP_COUNT);
- END LIST_EMP;
- —————————– 插入僱員過程 —————————–
- PROCEDUREINSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,P_SAL NUMBER)
- IS
- BEGIN
- IF NOT EXIST_EMP(P_EMPNO) THEN
- INSERT INTO EMP(EMPNO,ENAME,JOB,SAL) VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL);
- COMMIT;
- V_EMP_COUNT:=V_EMP_COUNT+1;
- V_MESSAGE:=’僱員’||P_EMPNO||‘已插入!’;
- ELSE
- V_MESSAGE:=’僱員’||P_EMPNO||‘已存在,不能插入!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||‘插入失敗!’;
- SHOW_MESSAGE;
- END INSERT_EMP;
- ————————— 刪除僱員過程 ——————–
- PROCEDURE DELETE_EMP(P_EMPNO NUMBER)
- IS
- BEGIN
- IF EXIST_EMP(P_EMPNO) THEN
- DELETE FROM EMP WHERE EMPNO=P_EMPNO;
- COMMIT;
- V_EMP_COUNT:=V_EMP_COUNT-1;
- V_MESSAGE:=’僱員’||P_EMPNO||‘已刪除!’;
- ELSE
- V_MESSAGE:=’僱員’||P_EMPNO||‘不存在,不能刪除!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||‘刪除失敗!’;
- SHOW_MESSAGE;
- END DELETE_EMP;
- ————————————— 修改僱員工資過程 ————————————
- PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER)
- IS
- BEGIN
- IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN
- V_MESSAGE:=’工資超出修改範圍!’;
- ELSIF NOT EXIST_EMP(P_EMPNO) THEN
- V_MESSAGE:=’僱員’||P_EMPNO||‘不存在,不能修改工資!’;
- ELSE
- UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO;
- COMMIT;
- V_MESSAGE:=’僱員’||P_EMPNO||‘工資已經修改!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||‘工資修改失敗!’;
- SHOW_MESSAGE;
- END CHANGE_EMP_SAL;
- —————————- 顯示信息過程 —————————-
- PROCEDURE SHOW_MESSAGE
- IS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’提示信息:’||V_MESSAGE);
- END SHOW_MESSAGE;
- ———————— 判斷僱員是否存在函數 ——————-
- FUNCTION EXIST_EMP(P_EMPNO NUMBER)
- RETURN BOOLEAN
- IS
- V_NUM NUMBER; –局部變量
- BEGIN
- SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO;
- IF V_NUM=1 THEN
- RETURN TRUE;
- ELSE
- RETURN FALSE;
- END IF;
- END EXIST_EMP;
- —————————–
- END EMP_PK;
結果爲:
- 程序包已創建。
- 程序包主體已創建。
- 程序包已創建。
- 程序包主體已創建。
步驟2:初始化包:
- SET SERVEROUTPUT ON
- EXECUTE EMP_PK.INIT(6000,600);
- SET SERVEROUTPUT ON
- EXECUTE EMP_PK.INIT(6000,600);
顯示爲:
- 提示信息:初始化過程已經完成!
- 提示信息:初始化過程已經完成!
步驟3:顯示僱員列表:
- EXECUTE EMP_PK.LIST_EMP;
- EXECUTE EMP_PK.LIST_EMP;
顯示爲:
- 姓名 職務 工資
- SMITH CLERK 1560
- ALLEN SALESMAN 1936
- WARD SALESMAN 1830
- JONES MANAGER 2975
- …
- 僱員總人數:14
- PL/SQL 過程已成功完成。
- 姓名 職務 工資
- SMITH CLERK 1560
- ALLEN SALESMAN 1936
- WARD SALESMAN 1830
- JONES MANAGER 2975
- …
- 僱員總人數:14
- PL/SQL 過程已成功完成。
步驟4:插入一個新記錄:
- EXECUTE EMP_PK.INSERT_EMP(8001,’小王’,’CLERK’,1000);
- EXECUTE EMP_PK.INSERT_EMP(8001,‘小王’,‘CLERK’,1000);
顯示結果爲:
- 提示信息:僱員8001已插入!
- PL/SQL 過程已成功完成。
- 提示信息:僱員8001已插入!
- PL/SQL 過程已成功完成。
步驟5:通過全局變量V_EMP_COUNT查看僱員人數:
- BEGIN
- DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT);
- END;
- BEGIN
- DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT);
- END;
顯示結果爲:
- 15
- PL/SQL 過程已成功完成。
- 15
- PL/SQL 過程已成功完成。
步驟6:刪除新插入記錄:
- EXECUTE EMP_PK.DELETE_EMP(8001);
- EXECUTE EMP_PK.DELETE_EMP(8001);
顯示結果爲:
- 提示信息:僱員8001已刪除!
- PL/SQL 過程已成功完成。
- 提示信息:僱員8001已刪除!
- PL/SQL 過程已成功完成。
再次刪除該僱員:
- EXECUTE EMP_PK.DELETE_EMP(8001);
- EXECUTE EMP_PK.DELETE_EMP(8001);
結果爲:
- 提示信息:僱員8001不存在,不能刪除!
- 提示信息:僱員8001不存在,不能刪除!
步驟7:修改僱員工資:
- EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000);
- EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000);
顯示結果爲:
- 提示信息:工資超出修改範圍!
- PL/SQL 過程已成功完成。
- 提示信息:工資超出修改範圍!
- PL/SQL 過程已成功完成。
步驟8:授權其他用戶調用包:
如果是另外一個用戶要使用該包,必須由包的所有者授權,下面授予STUDEN賬戶對該包的使用權:
- GRANT EXECUTE ON EMP_PK TO STUDENT;
- GRANT EXECUTE ON EMP_PK TO STUDENT;
每一個新的會話要爲包中的公用變量開闢新的存儲空間,所以需要重新執行初始化過程。兩個會話的進程互不影響。
步驟9:其他用戶調用包。
啓動另外一個SQL*Plus,登錄STUDENT賬戶,執行以下過程:
- SET SERVEROUTPUT ON
- EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700);
- SET SERVEROUTPUT ON
- EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700);
結果爲:
- 提示信息:初始化過程已經完成!
- PL/SQL 過程已成功完成。
- 提示信息:初始化過程已經完成!
- PL/SQL 過程已成功完成。
說明:在初始化中設置僱員的總人數和修改工資的上、下限,初始化後V_EMP_COUNT爲14人,插入僱員後V_EMP_COUNT爲15人。V_EMP_COUNT爲公有變量,所以可以在外部程序中使用DBMS_OUTPUT.PUT_LINE輸出,引用時用EMP_PK.V_EMP_COUNT的形式,說明所屬的包。而私有變量V_MAX_SAL和V_MIN_SAL不能被外部訪問,只能通過內部過程來修改。同樣,EXIST_EMP和SHOW_MESSAGE也是私有過程,也只能在過程體內被其他模塊引用。
注意:在最後一個步驟中,因爲STUDENT模式調用了SCOTT模式的包,所以包名前要增加模式名SCOTT。不同的會話對包的調用屬於不同的應用,所以需要重新進行初始化。
練習
1.如果存儲過程的參數類型爲OUT,那麼調用時傳遞的參數應該爲:
A.常量 B.表達式 C.變量 D.都可以
2.下列有關存儲過程的特點說法錯誤的是:
A.存儲過程不能將值傳回調用的主程序
B.存儲過程是一個命名的模塊
C.編譯的存儲過程存放在數據庫中
D.一個存儲過程可以調用另一個存儲過程
3.下列有關函數的特點說法錯誤的是:
A.函數必須定義返回類型
B.函數參數的類型只能是IN
C.在函數體內可以多次使用RETURN語句
D.函數的調用應使用EXECUTE命令
4.包中不能包含的元素爲:
A.存儲過程 B.存儲函數
C.遊標 D.表
5.下列有關包的使用說法錯誤的是:
A.在不同的包內模塊可以重名
B.包的私有過程不能被外部程序調用
C.包體中的過程和函數必須在包頭部分說明
D.必須先創建包頭,然後創建包體
oracle存儲過程實例
認識存儲過程和函數存儲過程和函數也是一種PL/SQL塊,是存入數據庫的PL/SQL塊。但存儲過程和函數不同於已經介紹過的PL/SQL程序,我們通常把PL/SQL程序稱爲無名塊,而存儲過程和函數是以命名的方式存儲於數據庫中的。和PL/SQL程序相比,存儲過程有很多優點,具體歸納如下:
* 存儲過程和函數以命名的數據庫對象形式存儲於數據庫當中。存儲在數據庫中的優點是很明顯的,因爲代碼不保存在本地,用戶可以在任何客戶機上登錄到數據庫,並調用或修改代碼。
* 存儲過程和函數可由數據庫提供安全保證,要想使用存儲過程和函數,需要有存儲過程和函數的所有者的授權,只有被授權的用戶或創建者本身才能執行存儲過程或調用函數。
* 存儲過程和函數的信息是寫入數據字典的,所以存儲過程可以看作是一個公用模塊,用戶編寫的PL/SQL程序或其他存儲過程都可以調用它(但存儲過程和函數不能調用PL/SQL程序)。一個重複使用的功能,可以設計成爲存儲過程,比如:顯示一張工資統計表,可以設計成爲存儲過程;一個經常調用的計算,可以設計成爲存儲函數;根據僱員編號返回僱員的姓名,可以設計成存儲函數。
* 像其他高級語言的過程和函數一樣,可以傳遞參數給存儲過程或函數,參數的傳遞也有多種方式。存儲過程可以有返回值,也可以沒有返回值,存儲過程的返回值必須通過參數帶回;函數有一定的數據類型,像其他的標準函數一樣,我們可以通過對函數名的調用返回函數值。
存儲過程和函數需要進行編譯,以排除語法錯誤,只有編譯通過才能調用。
創建和刪除存儲過程
創建存儲過程,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系統權限。該權限可由系統管理員授予。創建一個存儲過程的基本語句如下:
CREATE [OR REPLACE] PROCEDURE 存儲過程名[(參數[IN|OUT|IN OUT] 數據類型…)]
{AS|IS}
[說明部分]
BEGIN
可執行部分
[EXCEPTION
錯誤處理部分]
END [過程名];
其中:
可選關鍵字OR REPLACE 表示如果存儲過程已經存在,則用新的存儲過程覆蓋,通常用於存儲過程的重建。
參數部分用於定義多個參數(如果沒有參數,就可以省略)。參數有三種形式:IN、OUT和IN OUT。如果沒有指明參數的形式,則默認爲IN。
關鍵字AS也可以寫成IS,後跟過程的說明部分,可以在此定義過程的局部變量。
編寫存儲過程可以使用任何文本編輯器或直接在SQL*Plus環境下進行,編寫好的存儲過程必須要在SQL*Plus環境下進行編譯,生成編譯代碼,原代碼和編譯代碼在編譯過程中都會被存入數據庫。編譯成功的存儲過程就可以在Oracle環境下進行調用了。
一個存儲過程在不需要時可以刪除。刪除存儲過程的人是過程的創建者或者擁有DROP ANY PROCEDURE系統權限的人。刪除存儲過程的語法如下:
DROP PROCEDURE 存儲過程名;
如果要重新編譯一個存儲過程,則只能是過程的創建者或者擁有ALTER ANY PROCEDURE系統權限的人。語法如下:
ALTER PROCEDURE 存儲過程名 COMPILE;
執行(或調用)存儲過程的人是過程的創建者或是擁有EXECUTE ANY PROCEDURE系統權限的人或是被擁有者授予EXECUTE權限的人。執行的方法如下:
方法1:
EXECUTE 模式名.存儲過程名[(參數…)];
方法2:
BEGIN
模式名.存儲過程名[(參數…)];
END;
傳遞的參數必須與定義的參數類型、個數和順序一致(如果參數定義了默認值,則調用時可以省略參數)。參數可以是變量、常量或表達式,用法參見下一節。
如果是調用本賬戶下的存儲過程,則模式名可以省略。要調用其他賬戶編寫的存儲過程,則模式名必須要添加。
以下是一個生成和調用簡單存儲過程的訓練。注意要事先授予創建存儲過程的權限。
【訓練1】 創建一個顯示僱員總人數的存儲過程。
步驟1:登錄SCOTT賬戶(或學生個人賬戶)。
步驟2:在SQL*Plus輸入區中,輸入以下存儲過程:
- CREATE OR REPLACE PROCEDURE EMP_COUNT
- AS
- V_TOTAL NUMBER(10);
- BEGIN
- SELECT COUNT(*) INTO V_TOTAL FROM EMP;
- DBMS_OUTPUT.PUT_LINE(’僱員總人數爲:’||V_TOTAL);
- END;
- CREATE OR REPLACE PROCEDURE EMP_COUNT
- AS
- V_TOTAL NUMBER(10);
- BEGIN
- SELECT COUNT(*) INTO V_TOTAL FROM EMP;
- DBMS_OUTPUT.PUT_LINE(’僱員總人數爲:’||V_TOTAL);
- END;
步驟3:按“執行”按鈕進行編譯。
如果存在錯誤,就會顯示:
警告: 創建的過程帶有編譯錯誤。
如果存在錯誤,對腳本進行修改,直到沒有錯誤產生。
如果編譯結果正確,將顯示:
- 過程已創建。
- 過程已創建。
步驟4:調用存儲過程,在輸入區中輸入以下語句並執行:
- EXECUTE EMP_COUNT;
- EXECUTE EMP_COUNT;
顯示結果爲:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
說明:在該訓練中,V_TOTAL變量是存儲過程定義的局部變量,用於接收查詢到的僱員總人數。
注意:在SQL*Plus中輸入存儲過程,按“執行”按鈕是進行編譯,不是執行存儲過程。
如果在存儲過程中引用了其他用戶的對象,比如表,則必須有其他用戶授予的對象訪問權限。一個存儲過程一旦編譯成功,就可以由其他用戶或程序來引用。但存儲過程或函數的所有者必須授予其他用戶執行該過程的權限。
存儲過程沒有參數,在調用時,直接寫過程名即可。
【訓練2】 在PL/SQL程序中調用存儲過程。
步驟1:登錄SCOTT賬戶。
步驟2:授權STUDENT賬戶使用該存儲過程,即在SQL*Plus輸入區中,輸入以下的命令:
- GRANT EXECUTE ON EMP_COUNT TO STUDENT
- GRANT EXECUTE ON EMP_COUNT TO STUDENT
- 授權成功。
- 授權成功。
步驟3:登錄STUDENT賬戶,在SQL*Plus輸入區中輸入以下程序:
- SET SERVEROUTPUT ON
- BEGIN
- SCOTT.EMP_COUNT;
- END;
- SET SERVEROUTPUT ON
- BEGIN
- SCOTT.EMP_COUNT;
- END;
步驟4:執行以上程序,結果爲:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
說明:在本例中,存儲過程是由SCOTT賬戶創建的,STUDEN賬戶獲得SCOTT賬戶的授權後,才能調用該存儲過程。
注意:在程序中調用存儲過程,使用了第二種語法。
【訓練3】 編寫顯示僱員信息的存儲過程EMP_LIST,並引用EMP_COUNT存儲過程。
步驟1:在SQL*Plus輸入區中輸入並編譯以下存儲過程:
- CREATE OR REPLACE PROCEDURE EMP_LIST
- AS
- CURSOR emp_cursor IS
- SELECT empno,ename FROM emp;
- BEGIN
- FOR Emp_record IN emp_cursor LOOP
- DBMS_OUTPUT.PUT_LINE(Emp_record.empno||Emp_record.ename);
- END LOOP;
- EMP_COUNT;
- END;
- CREATE OR REPLACE PROCEDURE EMP_LIST
- AS
- CURSOR emp_cursor IS
- SELECT empno,ename FROM emp;
- BEGIN
- FOR Emp_record IN emp_cursor LOOP
- DBMS_OUTPUT.PUT_LINE(Emp_record.empno||Emp_record.ename);
- END LOOP;
- EMP_COUNT;
- END;
執行結果:
- 過程已創建。
- 過程已創建。
步驟2:調用存儲過程,在輸入區中輸入以下語句並執行:
- EXECUTE EMP_LIST
- EXECUTE EMP_LIST
顯示結果爲:
- 7369SMITH
- 7499ALLEN
- 7521WARD
- 7566JONES
- 執行結果:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
- 7369SMITH
- 7499ALLEN
- 7521WARD
- 7566JONES
- 執行結果:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
說明:以上的EMP_LIST存儲過程中定義並使用了遊標,用來循環顯示所有僱員的信息。然後調用已經成功編譯的存儲過程EMP_COUNT,用來附加顯示僱員總人數。通過EXECUTE命令來執行EMP_LIST存儲過程。
【練習1】編寫顯示部門信息的存儲過程DEPT_LIST,要求統計出部門個數。
參數傳遞
參數的作用是向存儲過程傳遞數據,或從存儲過程獲得返回結果。正確的使用參數可以大大增加存儲過程的靈活性和通用性。
參數的類型有三種,如下所示。
- IN 定義一個輸入參數變量,用於傳遞參數給存儲過程
- OUT 定義一個輸出參數變量,用於從存儲過程獲取數據
- IN OUT 定義一個輸入、輸出參數變量,兼有以上兩者的功能
- IN 定義一個輸入參數變量,用於傳遞參數給存儲過程
- OUT 定義一個輸出參數變量,用於從存儲過程獲取數據
- IN OUT 定義一個輸入、輸出參數變量,兼有以上兩者的功能
參數的定義形式和作用如下:
參數名 IN 數據類型 DEFAULT 值;
定義一個輸入參數變量,用於傳遞參數給存儲過程。在調用存儲過程時,主程序的實際參數可以是常量、有值變量或表達式等。DEFAULT 關鍵字爲可選項,用來設定參數的默認值。如果在調用存儲過程時不指明參數,則參數變量取默認值。在存儲過程中,輸入變量接收主程序傳遞的值,但不能對其進行賦值。
參數名 OUT 數據類型;
定義一個輸出參數變量,用於從存儲過程獲取數據,即變量從存儲過程中返回值給主程序。
在調用存儲過程時,主程序的實際參數只能是一個變量,而不能是常量或表達式。在存儲過程中,參數變量只能被賦值而不能將其用於賦值,在存儲過程中必須給輸出變量至少賦值一次。
參數名 IN OUT 數據類型 DEFAULT 值;
定義一個輸入、輸出參數變量,兼有以上兩者的功能。在調用存儲過程時,主程序的實際參數只能是一個變量,而不能是常量或表達式。DEFAULT 關鍵字爲可選項,用來設定參數的默認值。在存儲過程中,變量接收主程序傳遞的值,同時可以參加賦值運算,也可以對其進行賦值。在存儲過程中必須給變量至少賦值一次。
如果省略IN、OUT或IN OUT,則默認模式是IN。
【訓練1】 編寫給僱員增加工資的存儲過程CHANGE_SALARY,通過IN類型的參數傳遞要增加工資的僱員編號和增加的工資額。
步驟1:登錄SCOTT賬戶。
步驟2:在SQL*Plus輸入區中輸入以下存儲過程並執行:
- CREATE OR REPLACE PROCEDURE CHANGE_SALARY(P_EMPNO IN NUMBER DEFAULT 7788,P_RAISE NUMBER DEFAULT 10)
- AS
- V_ENAME VARCHAR2(10);
- V_SAL NUMBER(5);
- BEGIN
- SELECT ENAME,SAL INTO V_ENAME,V_SAL FROM EMP WHERE EMPNO=P_EMPNO;
- UPDATE EMP SET SAL=SAL+P_RAISE WHERE EMPNO=P_EMPNO;
- DBMS_OUTPUT.PUT_LINE(’僱員’||V_ENAME||’的工資被改爲’||TO_CHAR(V_SAL+P_RAISE));
- COMMIT;
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’發生錯誤,修改失敗!’);
- ROLLBACK;
- END;
- CREATE OR REPLACE PROCEDURE CHANGE_SALARY(P_EMPNO IN NUMBER DEFAULT 7788,P_RAISE NUMBER DEFAULT 10)
- AS
- V_ENAME VARCHAR2(10);
- V_SAL NUMBER(5);
- BEGIN
- SELECT ENAME,SAL INTO V_ENAME,V_SAL FROM EMP WHERE EMPNO=P_EMPNO;
- UPDATE EMP SET SAL=SAL+P_RAISE WHERE EMPNO=P_EMPNO;
- DBMS_OUTPUT.PUT_LINE(’僱員’||V_ENAME||‘的工資被改爲’||TO_CHAR(V_SAL+P_RAISE));
- COMMIT;
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’發生錯誤,修改失敗!’);
- ROLLBACK;
- END;
執行結果爲:
- 過程已創建。
- 過程已創建。
步驟3:調用存儲過程,在輸入區中輸入以下語句並執行:
- EXECUTE CHANGE_SALARY(7788,80)
- EXECUTE CHANGE_SALARY(7788,80)
顯示結果爲:
- 僱員SCOTT的工資被改爲3080
- 僱員SCOTT的工資被改爲3080
說明:從執行結果可以看到,僱員SCOTT的工資已由原來的3000改爲3080。
參數的值由調用者傳遞,傳遞的參數的個數、類型和順序應該和定義的一致。如果順序不一致,可以採用以下調用方法。如上例,執行語句可以改爲:
EXECUTE CHANGE_SALARY(P_RAISE=>80,P_EMPNO=>7788);
可以看出傳遞參數的順序發生了變化,並且明確指出了參數名和要傳遞的值,=>運算符左側是參數名,右側是參數表達式,這種賦值方法的意義較清楚。
【練習1】創建插入僱員的存儲過程INSERT_EMP,並將僱員編號等作爲參數。
在設計存儲過程的時候,也可以爲參數設定默認值,這樣調用者就可以不傳遞或少傳遞參數了。
【訓練2】 調用存儲過程CHANGE_SALARY,不傳遞參數,使用默認參數值。
在SQL*Plus輸入區中輸入以下命令並執行:
- EXECUTE CHANGE_SALARY
- EXECUTE CHANGE_SALARY
顯示結果爲:
- 僱員SCOTT的工資被改爲3090
- 僱員SCOTT的工資被改爲3090
說明:在存儲過程的調用中沒有傳遞參數,而是採用了默認值7788和10,即默認僱員號爲7788,增加的工資爲10。
【訓練3】 使用OUT類型的參數返回存儲過程的結果。
步驟1:登錄SCOTT賬戶。
步驟2:在SQL*Plus輸入區中輸入並編譯以下存儲過程:
- CREATE OR REPLACE PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)
- AS
- BEGIN
- SELECT COUNT(*) INTO P_TOTAL FROM EMP;
- END;
- CREATE OR REPLACE PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)
- AS
- BEGIN
- SELECT COUNT(*) INTO P_TOTAL FROM EMP;
- END;
執行結果爲:
- 過程已創建。
- 過程已創建。
步驟3:輸入以下程序並執行:
- DECLARE
- V_EMPCOUNT NUMBER;
- BEGIN
- EMP_COUNT(V_EMPCOUNT);
- DBMS_OUTPUT.PUT_LINE(’僱員總人數爲:’||V_EMPCOUNT);
- END;
- DECLARE
- V_EMPCOUNT NUMBER;
- BEGIN
- EMP_COUNT(V_EMPCOUNT);
- DBMS_OUTPUT.PUT_LINE(’僱員總人數爲:’||V_EMPCOUNT);
- END;
顯示結果爲:
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
- 僱員總人數爲:14
- PL/SQL 過程已成功完成。
說明:在存儲過程中定義了OUT類型的參數P_TOTAL,在主程序調用該存儲過程時,傳遞了參數V_EMPCOUNT。在存儲過程中的SELECT…INTO…語句中對P_TOTAL進行賦值,賦值結果由V_EMPCOUNT變量帶回給主程序並顯示。
以上程序要覆蓋同名的EMP_COUNT存儲過程,如果不使用OR REPLACE選項,就會出現以下錯誤:
- ERROR 位於第 1 行:
- ORA-00955: 名稱已由現有對象使用。
- ERROR 位於第 1 行:
- ORA-00955: 名稱已由現有對象使用。
【練習2】創建存儲過程,使用OUT類型參數獲得僱員經理名。
【訓練4】 使用IN OUT類型的參數,給電話號碼增加區碼。
步驟1:登錄SCOTT賬戶。
步驟2:在SQL*Plus輸入區中輸入並編譯以下存儲過程:
- CREATE OR REPLACE PROCEDURE ADD_REGION(P_HPONE_NUM IN OUT VARCHAR2)
- AS
- BEGIN
- P_HPONE_NUM:=’0755-‘||P_HPONE_NUM;
- END;
- CREATE OR REPLACE PROCEDURE ADD_REGION(P_HPONE_NUM IN OUT VARCHAR2)
- AS
- BEGIN
- P_HPONE_NUM:=’0755-‘||P_HPONE_NUM;
- END;
執行結果爲:
- 過程已創建。
- 過程已創建。
步驟3:輸入以下程序並執行:
- SET SERVEROUTPUT ON
- DECLARE
- V_PHONE_NUM VARCHAR2(15);
- BEGIN
- V_PHONE_NUM:=’26731092’;
- ADD_REGION(V_PHONE_NUM);
- DBMS_OUTPUT.PUT_LINE(’新的電話號碼:’||V_PHONE_NUM);
- END;
- SET SERVEROUTPUT ON
- DECLARE
- V_PHONE_NUM VARCHAR2(15);
- BEGIN
- V_PHONE_NUM:=’26731092’;
- ADD_REGION(V_PHONE_NUM);
- DBMS_OUTPUT.PUT_LINE(’新的電話號碼:’||V_PHONE_NUM);
- END;
顯示結果爲:
- 新的電話號碼:0755-26731092
- PL/SQL 過程已成功完成。
- 新的電話號碼:0755-26731092
- PL/SQL 過程已成功完成。
說明:變量V_HPONE_NUM既用來向存儲過程傳遞舊電話號碼,也用來向主程序返回新號碼。新的號碼在原來基礎上增加了區號0755和-。
創建和刪除存儲函數
創建函數,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系統權限。該權限可由系統管理員授予。創建存儲函數的語法和創建存儲過程的類似,即
CREATE [OR REPLACE] FUNCTION 函數名[(參數[IN] 數據類型…)]
RETURN 數據類型
{AS|IS}
[說明部分]
BEGIN
可執行部分
RETURN (表達式)
[EXCEPTION
錯誤處理部分]
END [函數名];
其中,參數是可選的,但只能是IN類型(IN關鍵字可以省略)。
在定義部分的RETURN 數據類型,用來表示函數的數據類型,也就是返回值的類型,此部分不可省略。
在可執行部分的RETURN(表達式),用來生成函數的返回值,其表達式的類型應該和定義部分說明的函數返回值的數據類型一致。在函數的執行部分可以有多個RETURN語句,但只有一個RETURN語句會被執行,一旦執行了RETURN語句,則函數結束並返回調用環境。
一個存儲函數在不需要時可以刪除,但刪除的人應是函數的創建者或者是擁有DROP ANY PROCEDURE系統權限的人。其語法如下:
DROP FUNCTION 函數名;
重新編譯一個存儲函數時,編譯的人應是函數的創建者或者擁有ALTER ANY PROCEDURE系統權限的人。重新編譯一個存儲函數的語法如下:
ALTER PROCEDURE 函數名 COMPILE;
函數的調用者應是函數的創建者或擁有EXECUTE ANY PROCEDURE系統權限的人,或是被函數的擁有者授予了函數執行權限的賬戶。函數的引用和存儲過程不同,函數要出現在程序體中,可以參加表達式的運算或單獨出現在表達式中,其形式如下:
變量名:=函數名(…)
【訓練1】 創建一個通過僱員編號返回僱員名稱的函數GET_EMP_NAME。
步驟1:登錄SCOTT賬戶。
步驟2:在SQL*Plus輸入區中輸入以下存儲函數並編譯:
- CREATE OR REPLACE FUNCTION GET_EMP_NAME(P_EMPNO NUMBER DEFAULT 7788)
- RETURN VARCHAR2
- AS
- V_ENAME VARCHAR2(10);
- BEGIN
- ELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO=P_EMPNO;
- RETURN(V_ENAME);
- EXCEPTION
- WHEN NO_DATA_FOUND THEN
- DBMS_OUTPUT.PUT_LINE(’沒有該編號僱員!’);
- RETURN (NULL);
- WHEN TOO_MANY_ROWS THEN
- DBMS_OUTPUT.PUT_LINE(’有重複僱員編號!’);
- RETURN (NULL);
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’發生其他錯誤!’);
- RETURN (NULL);
- END;
- CREATE OR REPLACE FUNCTION GET_EMP_NAME(P_EMPNO NUMBER DEFAULT 7788)
- RETURN VARCHAR2
- AS
- V_ENAME VARCHAR2(10);
- BEGIN
- ELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO=P_EMPNO;
- RETURN(V_ENAME);
- EXCEPTION
- WHEN NO_DATA_FOUND THEN
- DBMS_OUTPUT.PUT_LINE(’沒有該編號僱員!’);
- RETURN (NULL);
- WHEN TOO_MANY_ROWS THEN
- DBMS_OUTPUT.PUT_LINE(’有重複僱員編號!’);
- RETURN (NULL);
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’發生其他錯誤!’);
- RETURN (NULL);
- END;
步驟3:調用該存儲函數,輸入並執行以下程序:
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’僱員7369的名稱是:’|| GET_EMP_NAME(7369));
- DBMS_OUTPUT.PUT_LINE(’僱員7839的名稱是:’|| GET_EMP_NAME(7839));
- END;
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’僱員7369的名稱是:’|| GET_EMP_NAME(7369));
- DBMS_OUTPUT.PUT_LINE(’僱員7839的名稱是:’|| GET_EMP_NAME(7839));
- END;
顯示結果爲:
- 僱員7369的名稱是:SMITH
- 僱員7839的名稱是:KING
- PL/SQL 過程已成功完成。
- 僱員7369的名稱是:SMITH
- 僱員7839的名稱是:KING
- PL/SQL 過程已成功完成。
說明:函數的調用直接出現在程序的DBMS_OUTPUT.PUT_LINE語句中,作爲字符串表達式的一部分。如果輸入了錯誤的僱員編號,就會在函數的錯誤處理部分輸出錯誤信息。試修改僱員編號,重新運行調用部分。
【練習1】創建一個通過部門編號返回部門名稱的存儲函數GET_DEPT_NAME。
【練習2】將函數的執行權限授予STUDENT賬戶,然後登錄STUDENT賬戶調用。
存儲過程和函數的查看
可以通過對數據字典的訪問來查詢存儲過程或函數的有關信息,如果要查詢當前用戶的存儲過程或函數的源代碼,可以通過對USER_SOURCE數據字典視圖的查詢得到。USER_SOURCE的結構如下:
- DESCRIBE USER_SOURCE
- DESCRIBE USER_SOURCE
結果爲:
- 名稱 是否爲空? 類型
- ————————————————————- ————- ———————–
- NAME VARCHAR2(30)
- TYPE VARCHAR2(12)
- LINE NUMBER
- TEXT VARCHAR2(4000)
- 名稱 是否爲空? 類型
- ————————————————————- ————- ———————–
- NAME VARCHAR2(30)
- TYPE VARCHAR2(12)
- LINE NUMBER
- TEXT VARCHAR2(4000)
說明:裏面按行存放着過程或函數的腳本,NAME是過程或函數名,TYPE 代表類型(PROCEDURE或FUNCTION),LINE是行號,TEXT 爲腳本。
【訓練1】 查詢過程EMP_COUNT的腳本。
在SQL*Plus中輸入並執行如下查詢:
- select TEXT from user_source WHERE NAME=’EMP_COUNT’;
- select TEXT from user_source WHERE NAME=‘EMP_COUNT’;
結果爲:
- TEXT
- ——————————————————————————–
- PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)
- AS
- BEGIN
- SELECT COUNT(*) INTO P_TOTAL FROM EMP;
- END;
- TEXT
- ——————————————————————————–
- PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)
- AS
- BEGIN
- SELECT COUNT(*) INTO P_TOTAL FROM EMP;
- END;
【訓練2】 查詢過程GET_EMP_NAME的參數。
在SQL*Plus中輸入並執行如下查詢:
- DESCRIBE GET_EMP_NAME
- DESCRIBE GET_EMP_NAME
結果爲:
- FUNCTION GET_EMP_NAME RETURNS VARCHAR2
- 參數名稱 類型 輸入/輸出默認值?
- —————————————– ———————————– —————– ————-
- P_EMPNO NUMBER(4) IN DEFAULT
- FUNCTION GET_EMP_NAME RETURNS VARCHAR2
- 參數名稱 類型 輸入/輸出默認值?
- —————————————– ———————————– —————– ————-
- P_EMPNO NUMBER(4) IN DEFAULT
【訓練3】 在發生編譯錯誤時,顯示錯誤。
- SHOW ERRORS
- SHOW ERRORS
以下是一段編譯錯誤顯示:
- LINE/COL ERROR
- ————- —————————————————————–
- 4/2 PL/SQL: SQL Statement ignored
- 4/36 PLS-00201: 必須說明標識符 ’EMPP’
- LINE/COL ERROR
- ————- —————————————————————–
- 4/2 PL/SQL: SQL Statement ignored
- 4/36 PLS-00201: 必須說明標識符 ’EMPP’
說明:查詢一個存儲過程或函數是否是有效狀態(即編譯成功),可以使用數據字典USER_OBJECTS的STATUS列。
【訓練4】 查詢EMP_LIST存儲過程是否可用:
- SELECT STATUS FROM USER_OBJECTS WHERE OBJECT_NAME=’EMP_LIST’;
- SELECT STATUS FROM USER_OBJECTS WHERE OBJECT_NAME=‘EMP_LIST’;
結果爲:
- STATUS
- ————
- VALID
- STATUS
- ————
- VALID
說明:VALID表示該存儲過程有效(即通過編譯),INVALID表示存儲過程無效或需要重新編譯。當Oracle調用一個無效的存儲過程或函數時,首先試圖對其進行編譯,如果編譯成功則將狀態置成VALID並執行,否則給出錯誤信息。
當一個存儲過程編譯成功,狀態變爲VALID,會不會在某些情況下變成INVALID。結論是完全可能的。比如一個存儲過程中包含對錶的查詢,如果表被修改或刪除,存儲過程就會變成無效INVALID。所以要注意存儲過程和函數對其他對象的依賴關係。
如果要檢查存儲過程或函數的依賴性,可以通過查詢數據字典USER_DENPENDENCIES來確定,該表結構如下:
- DESCRIBE USER_DEPENDENCIES;
- DESCRIBE USER_DEPENDENCIES;
結果:
- 名稱 是否爲空? 類型
- ————————————————————– ————- —————————-
- NAME NOT NULL VARCHAR2(30)
- TYPE VARCHAR2(12)
- REFERENCED_OWNER VARCHAR2(30)
- REFERENCED_NAME VARCHAR2(64)
- REFERENCED_TYPE VARCHAR2(12)
- REFERENCED_LINK_NAME VARCHAR2(128)
- SCHEMAID NUMBER
- DEPENDENCY_TYPE VARCHAR2(4)
- 名稱 是否爲空? 類型
- ————————————————————– ————- —————————-
- NAME NOT NULL VARCHAR2(30)
- TYPE VARCHAR2(12)
- REFERENCED_OWNER VARCHAR2(30)
- REFERENCED_NAME VARCHAR2(64)
- REFERENCED_TYPE VARCHAR2(12)
- REFERENCED_LINK_NAME VARCHAR2(128)
- SCHEMAID NUMBER
- DEPENDENCY_TYPE VARCHAR2(4)
說明:NAME爲實體名,TYPE爲實體類型,REFERENCED_OWNER爲涉及到的實體擁有者賬戶,REFERENCED_NAME爲涉及到的實體名,REFERENCED_TYPE 爲涉及到的實體類型。
【訓練5】 查詢EMP_LIST存儲過程的依賴性。
- SELECT REFERENCED_NAME,REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE NAME=’EMP_LIST’;
- SELECT REFERENCED_NAME,REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE NAME=‘EMP_LIST’;
執行結果:
- REFERENCED_NAME REFERENCED_TYPE
- —————————————————————————————— —————————-
- STANDARD PACKAGE
- SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
- DBMS_OUTPUT PACKAGE
- DBMS_OUTPUT SYNONYM
- DBMS_OUTPUT NON-EXISTENT
- EMP TABLE
- EMP_COUNT PROCEDURE
- REFERENCED_NAME REFERENCED_TYPE
- —————————————————————————————— —————————-
- STANDARD PACKAGE
- SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
- DBMS_OUTPUT PACKAGE
- DBMS_OUTPUT SYNONYM
- DBMS_OUTPUT NON-EXISTENT
- EMP TABLE
- EMP_COUNT PROCEDURE
說明:可以看出存儲過程EMP_LIST依賴一些系統包、EMP表和EMP_COUNT存儲過程。如果刪除了EMP表或EMP_COUNT存儲過程,EMP_LIST將變成無效。
還有一種情況需要我們注意:如果一個用戶A被授予執行屬於用戶B的一個存儲過程的權限,在用戶B的存儲過程中,訪問到用戶C的表,用戶B被授予訪問用戶C的表的權限,但用戶A沒有被授予訪問用戶C表的權限,那麼用戶A調用用戶B的存儲過程是失敗的還是成功的呢?答案是成功的。如果讀者有興趣,不妨進行一下實際測試。
包
包的概念和組成
包是用來存儲相關程序結構的對象,它存儲於數據字典中。包由兩個分離的部分組成:包頭(PACKAGE)和包體(PACKAGE BODY)。包頭是包的說明部分,是對外的操作接口,對應用是可見的;包體是包的代碼和實現部分,對應用來說是不可見的黑盒。
包中可以包含的程序結構如下所示。
- 過程(PROCUDURE) 帶參數的命名的程序模塊
- 函數(FUNCTION) 帶參數、具有返回值的命名的程序模塊
- 變量(VARIABLE) 存儲變化的量的存儲單元
- 常量(CONSTANT) 存儲不變的量的存儲單元
- 遊標(CURSOR) 用戶定義的數據操作緩存區,在可執行部分使用
- 類型(TYPE) 用戶定義的新的結構類型
- 異常(EXCEPTION) 在標準包中定義或由用戶自定義,用於處理程序錯誤
- 過程(PROCUDURE) 帶參數的命名的程序模塊
- 函數(FUNCTION) 帶參數、具有返回值的命名的程序模塊
- 變量(VARIABLE) 存儲變化的量的存儲單元
- 常量(CONSTANT) 存儲不變的量的存儲單元
- 遊標(CURSOR) 用戶定義的數據操作緩存區,在可執行部分使用
- 類型(TYPE) 用戶定義的新的結構類型
- 異常(EXCEPTION) 在標準包中定義或由用戶自定義,用於處理程序錯誤
說明部分可以出現在包的三個不同的部分:出現在包頭中的稱爲公有元素,出現在包體中的稱爲私有元素,出現在包體的過程(或函數)中的稱爲局部變量。它們的性質有所不同,如下所示。
- 公有元素(PUBLIC) 在包頭中說明,在包體中具體定義 在包外可見並可以訪問,對整個應用的全過程有效
- 私有元素(PRIVATE) 在包體的說明部分說明 只能被包內部的其他部分訪問
- 局部變量(LOCAL) 在過程或函數的說明部分說明 只能在定義變量的過程或函數中使用
- 公有元素(PUBLIC) 在包頭中說明,在包體中具體定義 在包外可見並可以訪問,對整個應用的全過程有效
- 私有元素(PRIVATE) 在包體的說明部分說明 只能被包內部的其他部分訪問
- 局部變量(LOCAL) 在過程或函數的說明部分說明 只能在定義變量的過程或函數中使用
在包體中出現的過程或函數,如果需要對外公用,就必須在包頭中說明,包頭中的說明應該和包體中的說明一致。
包有以下優點:
* 包可以方便地將存儲過程和函數組織到一起,每個包又是相互獨立的。在不同的包中,過程、函數都可以重名,這解決了在同一個用戶環境中命名的衝突問題。
* 包增強了對存儲過程和函數的安全管理,對整個包的訪問權只需一次授予。
* 在同一個會話中,公用變量的值將被保留,直到會話結束。
* 區分了公有過程和私有過程,包體的私有過程增加了過程和函數的保密性。
* 包在被首次調用時,就作爲一個整體被全部調入內存,減少了多次訪問過程或函數的I/O次數。
創建包和包體
包由包頭和包體兩部分組成,包的創建應該先創建包頭部分,然後創建包體部分。創建、刪除和編譯包的權限同創建、刪除和編譯存儲過程的權限相同。
創建包頭的簡要語句如下:
CREATE [OR REPLACE] PACKAGE 包名
{IS|AS}
公有變量定義
公有類型定義
公有遊標定義
公有異常定義
函數說明
過程說明
END;
創建包體的簡要語法如下:
CREATE [OR REPLACE] PACKAGE BODY 包名
{IS|AS}
私有變量定義
私有類型定義
私有遊標定義
私有異常定義
函數定義
過程定義
END;
包的其他操作命令包括:
刪除包頭:
DROP PACKAGE 包頭名
刪除包體:
DROP PACKAGE BODY 包體名
重新編譯包頭:
ALTER PACKAGE 包名 COMPILE PACKAGE
重新編譯包體:
ALTER PACKAGE 包名 COMPILE PACKAGE BODY
在包頭中說明的對象可以在包外調用,調用的方法和調用單獨的過程或函數的方法基本相同,惟一的區別就是要在調用的過程或函數名前加上包的名字(中間用“.”分隔)。但要注意,不同的會話將單獨對包的公用變量進行初始化,所以不同的會話對包的調用屬於不同的應用。
系統包
Oracle預定義了很多標準的系統包,這些包可以在應用中直接使用,比如在訓練中我們使用的DBMS_OUTPUT包,就是系統包。PUT_LINE是該包的一個函數。常用系統包下所示。
- DBMS_OUTPUT 在SQL*Plus環境下輸出信息
- DBMS_DDL 編譯過程函數和包
- DBMS_SESSION 改變用戶的會話,初始化包等
- DBMS_TRANSACTION 控制數據庫事務
- DBMS_MAIL 連接Oracle*Mail
- DBMS_LOCK 進行復雜的鎖機制管理
- DBMS_ALERT 識別數據庫事件告警
- DBMS_PIPE 通過管道在會話間傳遞信息
- DBMS_JOB 管理Oracle的作業
- DBMS_LOB 操縱大對象
- DBMS_SQL 執行動態SQL語句
- DBMS_OUTPUT 在SQL*Plus環境下輸出信息
- DBMS_DDL 編譯過程函數和包
- DBMS_SESSION 改變用戶的會話,初始化包等
- DBMS_TRANSACTION 控制數據庫事務
- DBMS_MAIL 連接Oracle*Mail
- DBMS_LOCK 進行復雜的鎖機制管理
- DBMS_ALERT 識別數據庫事件告警
- DBMS_PIPE 通過管道在會話間傳遞信息
- DBMS_JOB 管理Oracle的作業
- DBMS_LOB 操縱大對象
- DBMS_SQL 執行動態SQL語句
包的應用
在SQL*Plus環境下,包和包體可以分別編譯,也可以一起編譯。如果分別編譯,則要先編譯包頭,後編譯包體。如果在一起編譯,則包頭寫在前,包體在後,中間用“/”分隔。
可以將已經存在的存儲過程或函數添加到包中,方法是去掉過程或函數創建語句的CREATE OR REPLACE部分,將存儲過程或函數複製到包體中 ,然後重新編譯即可。
如果需要將私有過程或函數變成共有過程或函數的話,將過程或函數說明部分複製到包頭說明部分,然後重新編譯就可以了。
【訓練1】 創建管理僱員信息的包EMPLOYE,它具有從EMP表獲得僱員信息,修改僱員名稱,修改僱員工資和寫回EMP表的功能。
步驟1:登錄SCOTT賬戶,輸入以下代碼並編譯:
- CREATE OR REPLACE PACKAGE EMPLOYE –包頭部分
- IS
- PROCEDURE SHOW_DETAIL;
- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER);
- PROCEDURE SAVE_EMPLOYE;
- PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2);
- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER);
- END EMPLOYE;
- /
- CREATE OR REPLACE PACKAGE BODY EMPLOYE –包體部分
- IS
- EMPLOYE EMP%ROWTYPE;
- ————– 顯示僱員信息 —————
- PROCEDURE SHOW_DETAIL
- AS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(‘—– 僱員信息 —–’);
- DBMS_OUTPUT.PUT_LINE(’僱員編號:’||EMPLOYE.EMPNO);
- DBMS_OUTPUT.PUT_LINE(’僱員名稱:’||EMPLOYE.ENAME);
- DBMS_OUTPUT.PUT_LINE(’僱員職務:’||EMPLOYE.JOB);
- DBMS_OUTPUT.PUT_LINE(’僱員工資:’||EMPLOYE.SAL);
- DBMS_OUTPUT.PUT_LINE(’部門編號:’||EMPLOYE.DEPTNO);
- END SHOW_DETAIL;
- —————– 從EMP表取得一個僱員 ——————–
- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER)
- AS
- BEGIN
- SELECT * INTO EMPLOYE FROM EMP WHERE EMPNO=P_EMPNO;
- DBMS_OUTPUT.PUT_LINE(’獲取僱員’||EMPLOYE.ENAME||’信息成功’);
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’獲取僱員信息發生錯誤!’);
- END GET_EMPLOYE;
- ———————- 保存僱員到EMP表 ————————–
- PROCEDURE SAVE_EMPLOYE
- AS
- BEGIN
- UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=
- EMPLOYE.EMPNO;
- DBMS_OUTPUT.PUT_LINE(’僱員信息保存完成!’);
- END SAVE_EMPLOYE;
- —————————- 修改僱員名稱 ——————————
- PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2)
- AS
- BEGIN
- EMPLOYE.ENAME:=P_NEWNAME;
- DBMS_OUTPUT.PUT_LINE(’修改名稱完成!’);
- END CHANGE_NAME;
- —————————- 修改僱員工資 ————————–
- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER)
- AS
- BEGIN
- EMPLOYE.SAL:=P_NEWSAL;
- DBMS_OUTPUT.PUT_LINE(’修改工資完成!’);
- END CHANGE_SAL;
- END EMPLOYE;
- CREATE OR REPLACE PACKAGE EMPLOYE –包頭部分
- IS
- PROCEDURE SHOW_DETAIL;
- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER);
- PROCEDURE SAVE_EMPLOYE;
- PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2);
- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER);
- END EMPLOYE;
- /
- CREATE OR REPLACE PACKAGE BODY EMPLOYE –包體部分
- IS
- EMPLOYE EMP%ROWTYPE;
- ————– 顯示僱員信息 —————
- PROCEDURE SHOW_DETAIL
- AS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(‘—– 僱員信息 —–’);
- DBMS_OUTPUT.PUT_LINE(’僱員編號:’||EMPLOYE.EMPNO);
- DBMS_OUTPUT.PUT_LINE(’僱員名稱:’||EMPLOYE.ENAME);
- DBMS_OUTPUT.PUT_LINE(’僱員職務:’||EMPLOYE.JOB);
- DBMS_OUTPUT.PUT_LINE(’僱員工資:’||EMPLOYE.SAL);
- DBMS_OUTPUT.PUT_LINE(’部門編號:’||EMPLOYE.DEPTNO);
- END SHOW_DETAIL;
- —————– 從EMP表取得一個僱員 ——————–
- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER)
- AS
- BEGIN
- SELECT * INTO EMPLOYE FROM EMP WHERE EMPNO=P_EMPNO;
- DBMS_OUTPUT.PUT_LINE(’獲取僱員’||EMPLOYE.ENAME||‘信息成功’);
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE(’獲取僱員信息發生錯誤!’);
- END GET_EMPLOYE;
- ———————- 保存僱員到EMP表 ————————–
- PROCEDURE SAVE_EMPLOYE
- AS
- BEGIN
- UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=
- EMPLOYE.EMPNO;
- DBMS_OUTPUT.PUT_LINE(’僱員信息保存完成!’);
- END SAVE_EMPLOYE;
- —————————- 修改僱員名稱 ——————————
- PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2)
- AS
- BEGIN
- EMPLOYE.ENAME:=P_NEWNAME;
- DBMS_OUTPUT.PUT_LINE(’修改名稱完成!’);
- END CHANGE_NAME;
- —————————- 修改僱員工資 ————————–
- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER)
- AS
- BEGIN
- EMPLOYE.SAL:=P_NEWSAL;
- DBMS_OUTPUT.PUT_LINE(’修改工資完成!’);
- END CHANGE_SAL;
- END EMPLOYE;
步驟2:獲取僱員7788的信息:
- SET SERVEROUTPUT ON
- EXECUTE EMPLOYE.GET_EMPLOYE(7788);
- SET SERVEROUTPUT ON
- EXECUTE EMPLOYE.GET_EMPLOYE(7788);
結果爲:
- 獲取僱員SCOTT信息成功
- PL/SQL 過程已成功完成。
- 獲取僱員SCOTT信息成功
- PL/SQL 過程已成功完成。
步驟3:顯示僱員信息:
- EXECUTE EMPLOYE.SHOW_DETAIL;
- EXECUTE EMPLOYE.SHOW_DETAIL;
結果爲:
- —————— 僱員信息 ——————
- 僱員編號:7788
- 僱員名稱:SCOTT
- 僱員職務:ANALYST
- 僱員工資:3000
- 部門編號:20
- PL/SQL 過程已成功完成。
- —————— 僱員信息 ——————
- 僱員編號:7788
- 僱員名稱:SCOTT
- 僱員職務:ANALYST
- 僱員工資:3000
- 部門編號:20
- PL/SQL 過程已成功完成。
步驟4:修改僱員工資:
- EXECUTE EMPLOYE.CHANGE_SAL(3800);
- EXECUTE EMPLOYE.CHANGE_SAL(3800);
結果爲:
- 修改工資完成!
- PL/SQL 過程已成功完成。
- 修改工資完成!
- PL/SQL 過程已成功完成。
步驟5:將修改的僱員信息存入EMP表
- EXECUTE EMPLOYE.SAVE_EMPLOYE;
- EXECUTE EMPLOYE.SAVE_EMPLOYE;
結果爲:
- 僱員信息保存完成!
- PL/SQL 過程已成功完成。
- 僱員信息保存完成!
- PL/SQL 過程已成功完成。
說明:該包完成將EMP表中的某個僱員的信息取入內存記錄變量,在記錄變量中進行修改編輯,在確認顯示信息正確後寫回EMP表的功能。記錄變量EMPLOYE用來存儲取得的僱員信息,定義爲私有變量,只能被包的內部模塊訪問。
【練習1】爲包增加修改僱員職務和部門編號的功能。
階段訓練
下面的訓練通過定義和創建完整的包EMP_PK並綜合運用本章的知識,完成對僱員表的插入、刪除等功能,包中的主要元素解釋如下所示。
- 程序結構 類 型 說 明
- V_EMP_COUNT 公有變量 跟蹤僱員的總人數變化,插入、刪除僱員的同時修改該變量的值
- INIT 公有過程 對包進行初始化,初始化僱員人數和工資修改的上、下限
- LIST_EMP 公有過程 顯示僱員列表
- INSERT_EMP 公有過程 通過編號插入新僱員
- DELETE_EMP 公有過程 通過編號刪除僱員
- CHANGE_EMP_SAL 公有過程 通過編號修改僱員工資
- V_MESSAGE 私有變量 存放準備輸出的信息
- C_MAX_SAL 私有變量 對工資修改的上限
- C_MIN_SAL 私有變量 對工資修改的下限
- SHOW_MESSAGE 私有過程 顯示私有變量V_MESSAGE中的信息
- EXIST_EMP 私有函數 判斷某個編號的僱員是否存在,該函數被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等過程調用
- 程序結構 類 型 說 明
- V_EMP_COUNT 公有變量 跟蹤僱員的總人數變化,插入、刪除僱員的同時修改該變量的值
- INIT 公有過程 對包進行初始化,初始化僱員人數和工資修改的上、下限
- LIST_EMP 公有過程 顯示僱員列表
- INSERT_EMP 公有過程 通過編號插入新僱員
- DELETE_EMP 公有過程 通過編號刪除僱員
- CHANGE_EMP_SAL 公有過程 通過編號修改僱員工資
- V_MESSAGE 私有變量 存放準備輸出的信息
- C_MAX_SAL 私有變量 對工資修改的上限
- C_MIN_SAL 私有變量 對工資修改的下限
- SHOW_MESSAGE 私有過程 顯示私有變量V_MESSAGE中的信息
- EXIST_EMP 私有函數 判斷某個編號的僱員是否存在,該函數被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等過程調用
【訓練1】 完整的僱員包EMP_PK的創建和應用。
步驟1:在SQL*Plus中登錄SCOTT賬戶,輸入以下包頭和包體部分,按“執行”按鈕編譯:
- CREATE OR REPLACE PACKAGE EMP_PK
- –包頭部分
- IS
- V_EMP_COUNT NUMBER(5);
- –僱員人數
- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER); –初始化
- PROCEDURE LIST_EMP;
- –顯示僱員列表
- PROCEDURE INSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,
- P_SAL NUMBER);
- –插入僱員
- PROCEDURE DELETE_EMP(P_EMPNO NUMBER); –刪除僱員
- PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER);
- –修改僱員工資
- END EMP_PK;
- /CREATE OR REPLACE PACKAGE BODY EMP_PK
- –包體部分
- IS
- V_MESSAGE VARCHAR2(50); –顯示信息
- V_MAX_SAL NUMBER(7); –工資上限
- V_MIN_SAL NUMBER(7); –工資下限
- FUNCTION EXIST_EMP(P_EMPNO NUMBER) RETURN BOOLEAN; –判斷僱員是否存在函數
- PROCEDURE SHOW_MESSAGE; –顯示信息過程
- ——————————- 初始化過程 —————————-
- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER)
- IS
- BEGIN
- SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP;
- V_MAX_SAL:=P_MAX;
- V_MIN_SAL:=P_MIN;
- V_MESSAGE:=’初始化過程已經完成!’;
- SHOW_MESSAGE;
- END INIT;
- —————————- 顯示僱員列表過程 ———————
- PROCEDURE LIST_EMP
- IS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’姓名 職務 工資’);
- FOR emp_rec IN (SELECT * FROM EMP)
- LOOP
- DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,”)||RPAD(emp_rec.job,10,’ ’)||TO_CHAR(emp_rec.sal));
- END LOOP;
- DBMS_OUTPUT.PUT_LINE(’僱員總人數’||V_EMP_COUNT);
- END LIST_EMP;
- —————————– 插入僱員過程 —————————–
- PROCEDUREINSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,P_SAL NUMBER)
- IS
- BEGIN
- IF NOT EXIST_EMP(P_EMPNO) THEN
- INSERT INTO EMP(EMPNO,ENAME,JOB,SAL) VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL);
- COMMIT;
- V_EMP_COUNT:=V_EMP_COUNT+1;
- V_MESSAGE:=’僱員’||P_EMPNO||’已插入!’;
- ELSE
- V_MESSAGE:=’僱員’||P_EMPNO||’已存在,不能插入!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||’插入失敗!’;
- SHOW_MESSAGE;
- END INSERT_EMP;
- ————————— 刪除僱員過程 ——————–
- PROCEDURE DELETE_EMP(P_EMPNO NUMBER)
- IS
- BEGIN
- IF EXIST_EMP(P_EMPNO) THEN
- DELETE FROM EMP WHERE EMPNO=P_EMPNO;
- COMMIT;
- V_EMP_COUNT:=V_EMP_COUNT-1;
- V_MESSAGE:=’僱員’||P_EMPNO||’已刪除!’;
- ELSE
- V_MESSAGE:=’僱員’||P_EMPNO||’不存在,不能刪除!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||’刪除失敗!’;
- SHOW_MESSAGE;
- END DELETE_EMP;
- ————————————— 修改僱員工資過程 ————————————
- PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER)
- IS
- BEGIN
- IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN
- V_MESSAGE:=’工資超出修改範圍!’;
- ELSIF NOT EXIST_EMP(P_EMPNO) THEN
- V_MESSAGE:=’僱員’||P_EMPNO||’不存在,不能修改工資!’;
- ELSE
- UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO;
- COMMIT;
- V_MESSAGE:=’僱員’||P_EMPNO||’工資已經修改!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||’工資修改失敗!’;
- SHOW_MESSAGE;
- END CHANGE_EMP_SAL;
- —————————- 顯示信息過程 —————————-
- PROCEDURE SHOW_MESSAGE
- IS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’提示信息:’||V_MESSAGE);
- END SHOW_MESSAGE;
- ———————— 判斷僱員是否存在函數 ——————-
- FUNCTION EXIST_EMP(P_EMPNO NUMBER)
- RETURN BOOLEAN
- IS
- V_NUM NUMBER; –局部變量
- BEGIN
- SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO;
- IF V_NUM=1 THEN
- RETURN TRUE;
- ELSE
- RETURN FALSE;
- END IF;
- END EXIST_EMP;
- —————————–
- END EMP_PK;
- CREATE OR REPLACE PACKAGE EMP_PK
- –包頭部分
- IS
- V_EMP_COUNT NUMBER(5);
- –僱員人數
- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER); –初始化
- PROCEDURE LIST_EMP;
- –顯示僱員列表
- PROCEDURE INSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,
- P_SAL NUMBER);
- –插入僱員
- PROCEDURE DELETE_EMP(P_EMPNO NUMBER); –刪除僱員
- PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER);
- –修改僱員工資
- END EMP_PK;
- /CREATE OR REPLACE PACKAGE BODY EMP_PK
- –包體部分
- IS
- V_MESSAGE VARCHAR2(50); –顯示信息
- V_MAX_SAL NUMBER(7); –工資上限
- V_MIN_SAL NUMBER(7); –工資下限
- FUNCTION EXIST_EMP(P_EMPNO NUMBER) RETURN BOOLEAN; –判斷僱員是否存在函數
- PROCEDURE SHOW_MESSAGE; –顯示信息過程
- ——————————- 初始化過程 —————————-
- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER)
- IS
- BEGIN
- SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP;
- V_MAX_SAL:=P_MAX;
- V_MIN_SAL:=P_MIN;
- V_MESSAGE:=’初始化過程已經完成!’;
- SHOW_MESSAGE;
- END INIT;
- —————————- 顯示僱員列表過程 ———————
- PROCEDURE LIST_EMP
- IS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’姓名 職務 工資’);
- FOR emp_rec IN (SELECT * FROM EMP)
- LOOP
- DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,”)||RPAD(emp_rec.job,10,‘ ’)||TO_CHAR(emp_rec.sal));
- END LOOP;
- DBMS_OUTPUT.PUT_LINE(’僱員總人數’||V_EMP_COUNT);
- END LIST_EMP;
- —————————– 插入僱員過程 —————————–
- PROCEDUREINSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,P_SAL NUMBER)
- IS
- BEGIN
- IF NOT EXIST_EMP(P_EMPNO) THEN
- INSERT INTO EMP(EMPNO,ENAME,JOB,SAL) VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL);
- COMMIT;
- V_EMP_COUNT:=V_EMP_COUNT+1;
- V_MESSAGE:=’僱員’||P_EMPNO||‘已插入!’;
- ELSE
- V_MESSAGE:=’僱員’||P_EMPNO||‘已存在,不能插入!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||‘插入失敗!’;
- SHOW_MESSAGE;
- END INSERT_EMP;
- ————————— 刪除僱員過程 ——————–
- PROCEDURE DELETE_EMP(P_EMPNO NUMBER)
- IS
- BEGIN
- IF EXIST_EMP(P_EMPNO) THEN
- DELETE FROM EMP WHERE EMPNO=P_EMPNO;
- COMMIT;
- V_EMP_COUNT:=V_EMP_COUNT-1;
- V_MESSAGE:=’僱員’||P_EMPNO||‘已刪除!’;
- ELSE
- V_MESSAGE:=’僱員’||P_EMPNO||‘不存在,不能刪除!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||‘刪除失敗!’;
- SHOW_MESSAGE;
- END DELETE_EMP;
- ————————————— 修改僱員工資過程 ————————————
- PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER)
- IS
- BEGIN
- IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN
- V_MESSAGE:=’工資超出修改範圍!’;
- ELSIF NOT EXIST_EMP(P_EMPNO) THEN
- V_MESSAGE:=’僱員’||P_EMPNO||‘不存在,不能修改工資!’;
- ELSE
- UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO;
- COMMIT;
- V_MESSAGE:=’僱員’||P_EMPNO||‘工資已經修改!’;
- END IF;
- SHOW_MESSAGE;
- EXCEPTION
- WHEN OTHERS THEN
- V_MESSAGE:=’僱員’||P_EMPNO||‘工資修改失敗!’;
- SHOW_MESSAGE;
- END CHANGE_EMP_SAL;
- —————————- 顯示信息過程 —————————-
- PROCEDURE SHOW_MESSAGE
- IS
- BEGIN
- DBMS_OUTPUT.PUT_LINE(’提示信息:’||V_MESSAGE);
- END SHOW_MESSAGE;
- ———————— 判斷僱員是否存在函數 ——————-
- FUNCTION EXIST_EMP(P_EMPNO NUMBER)
- RETURN BOOLEAN
- IS
- V_NUM NUMBER; –局部變量
- BEGIN
- SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO;
- IF V_NUM=1 THEN
- RETURN TRUE;
- ELSE
- RETURN FALSE;
- END IF;
- END EXIST_EMP;
- —————————–
- END EMP_PK;
結果爲:
- 程序包已創建。
- 程序包主體已創建。
- 程序包已創建。
- 程序包主體已創建。
步驟2:初始化包:
- SET SERVEROUTPUT ON
- EXECUTE EMP_PK.INIT(6000,600);
- SET SERVEROUTPUT ON
- EXECUTE EMP_PK.INIT(6000,600);
顯示爲:
- 提示信息:初始化過程已經完成!
- 提示信息:初始化過程已經完成!
步驟3:顯示僱員列表:
- EXECUTE EMP_PK.LIST_EMP;
- EXECUTE EMP_PK.LIST_EMP;
顯示爲:
- 姓名 職務 工資
- SMITH CLERK 1560
- ALLEN SALESMAN 1936
- WARD SALESMAN 1830
- JONES MANAGER 2975
- …
- 僱員總人數:14
- PL/SQL 過程已成功完成。
- 姓名 職務 工資
- SMITH CLERK 1560
- ALLEN SALESMAN 1936
- WARD SALESMAN 1830
- JONES MANAGER 2975
- …
- 僱員總人數:14
- PL/SQL 過程已成功完成。
步驟4:插入一個新記錄:
- EXECUTE EMP_PK.INSERT_EMP(8001,’小王’,’CLERK’,1000);
- EXECUTE EMP_PK.INSERT_EMP(8001,‘小王’,‘CLERK’,1000);
顯示結果爲:
- 提示信息:僱員8001已插入!
- PL/SQL 過程已成功完成。
- 提示信息:僱員8001已插入!
- PL/SQL 過程已成功完成。
步驟5:通過全局變量V_EMP_COUNT查看僱員人數:
- BEGIN
- DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT);
- END;
- BEGIN
- DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT);
- END;
顯示結果爲:
- 15
- PL/SQL 過程已成功完成。
- 15
- PL/SQL 過程已成功完成。
步驟6:刪除新插入記錄:
- EXECUTE EMP_PK.DELETE_EMP(8001);
- EXECUTE EMP_PK.DELETE_EMP(8001);
顯示結果爲:
- 提示信息:僱員8001已刪除!
- PL/SQL 過程已成功完成。
- 提示信息:僱員8001已刪除!
- PL/SQL 過程已成功完成。
再次刪除該僱員:
- EXECUTE EMP_PK.DELETE_EMP(8001);
- EXECUTE EMP_PK.DELETE_EMP(8001);
結果爲:
- 提示信息:僱員8001不存在,不能刪除!
- 提示信息:僱員8001不存在,不能刪除!
步驟7:修改僱員工資:
- EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000);
- EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000);
顯示結果爲:
- 提示信息:工資超出修改範圍!
- PL/SQL 過程已成功完成。
- 提示信息:工資超出修改範圍!
- PL/SQL 過程已成功完成。
步驟8:授權其他用戶調用包:
如果是另外一個用戶要使用該包,必須由包的所有者授權,下面授予STUDEN賬戶對該包的使用權:
- GRANT EXECUTE ON EMP_PK TO STUDENT;
- GRANT EXECUTE ON EMP_PK TO STUDENT;
每一個新的會話要爲包中的公用變量開闢新的存儲空間,所以需要重新執行初始化過程。兩個會話的進程互不影響。
步驟9:其他用戶調用包。
啓動另外一個SQL*Plus,登錄STUDENT賬戶,執行以下過程:
- SET SERVEROUTPUT ON
- EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700);
- SET SERVEROUTPUT ON
- EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700);
結果爲:
- 提示信息:初始化過程已經完成!
- PL/SQL 過程已成功完成。
- 提示信息:初始化過程已經完成!
- PL/SQL 過程已成功完成。
說明:在初始化中設置僱員的總人數和修改工資的上、下限,初始化後V_EMP_COUNT爲14人,插入僱員後V_EMP_COUNT爲15人。V_EMP_COUNT爲公有變量,所以可以在外部程序中使用DBMS_OUTPUT.PUT_LINE輸出,引用時用EMP_PK.V_EMP_COUNT的形式,說明所屬的包。而私有變量V_MAX_SAL和V_MIN_SAL不能被外部訪問,只能通過內部過程來修改。同樣,EXIST_EMP和SHOW_MESSAGE也是私有過程,也只能在過程體內被其他模塊引用。
注意:在最後一個步驟中,因爲STUDENT模式調用了SCOTT模式的包,所以包名前要增加模式名SCOTT。不同的會話對包的調用屬於不同的應用,所以需要重新進行初始化。
練習
1.如果存儲過程的參數類型爲OUT,那麼調用時傳遞的參數應該爲:
A.常量 B.表達式 C.變量 D.都可以
2.下列有關存儲過程的特點說法錯誤的是:
A.存儲過程不能將值傳回調用的主程序
B.存儲過程是一個命名的模塊
C.編譯的存儲過程存放在數據庫中
D.一個存儲過程可以調用另一個存儲過程
3.下列有關函數的特點說法錯誤的是:
A.函數必須定義返回類型
B.函數參數的類型只能是IN
C.在函數體內可以多次使用RETURN語句
D.函數的調用應使用EXECUTE命令
4.包中不能包含的元素爲:
A.存儲過程 B.存儲函數
C.遊標 D.表
5.下列有關包的使用說法錯誤的是:
A.在不同的包內模塊可以重名
B.包的私有過程不能被外部程序調用
C.包體中的過程和函數必須在包頭部分說明
D.必須先創建包頭,然後創建包體