Oracle數據庫之PL/SQL包
1. 簡介
包(PACKAGE)是一種數據對象,它是一組相關過程、函數、變量、常量和遊標等PL/SQL程序設計元素的組合,作爲一個完整的單元存儲在數據庫中,用名稱來標識。
包類似於JAVA或C#語言中的類,包中的變量相當於類中的成員變量,過程和函數相當於類方法。
通過使用包,可以簡化應用程序設計,提高應用性能,實現信息隱藏、子程序重載等面嚮對象語言所具有的功能。
與高級語言中的類相同,包中的程序元素也分爲公用元素和私用元素兩種,這兩種元素的區別是他們允許訪問的程序範圍不同。公用元素不僅可以被包中的函數、過程所調用,也可以被包外的PL/SQL程序訪問,而私有元素只能被包內的函數和過程序所訪問。
一般是先編寫獨立的過程與函數,待其較爲完善或經過充分驗證無誤後,再按邏輯相關性組織爲程序包。
2. 包的優點
- 模塊化:使用包,可以封裝相關的類型、對象和子程序。把一個大的功能模塊劃分成多個小的功能模塊,分別完成各自的功能,這樣組織的程序易於編寫,理解和管理。
- 更輕鬆的應用程序設計:包規範部分和包體部分可以分別創建並編譯。換言之,我們可以在沒有編寫包體的情況下編寫包規範的代碼並進行編譯。
- 信息隱藏:包中的元素可以分爲公有元素和私有元素,公有元素可被程序包內的過程、函數等訪問,還可以被包外的PL/SQL訪問。但對於私有元素只能被包內的過程、函數等訪問。對於用戶,只需知道包規範,不用瞭解包體的具體細節。
- 性能更佳:應用程序第一次調用程序包中的某個元素時,就將整個程序包加載到內存中,當第二次訪問程序包中的元素時,ORACLE將直接從內在中讀取,而不需要進行磁盤I/O操作而影響速度,同時位於內存中的程序包可被同一會話期間的其它應用程序共享。因此,程序包增加了重用性並改善了多用戶、多應用程序環境的效率。
3. 包的定義
PL/SQL中的包由包規範和包體兩部分組成。建立包時,首先要建立包規範,然後再建立對包規範的實現–包體。
包規範用於聲明包的公用組件,如變量、常量、自定義數據類型、異常、過程、函數、遊標等。包規範中定義的公有組件不僅可以在包內使用,還可以由包外其他過程、函數使用。但需要說明與注意的是,爲了實現信息的隱藏,建議不要將所有組件都放在包規範處聲明,只應把公共組件放在包規範部分。
包體是包的具體實現細節,它實現在包規範中聲明的所有公有過程、函數、遊標等。也可以在包體中聲明僅屬於自己的私有過程、函數、遊標等。
3.1 建立包規範
語法:
CREATE [ OR REPLACE ] [ EDITIONABLE | NONEDITIONABLE ]
PACKAGE [ schema. ] package_name
[ invoker_rights_clause ]
{ IS | AS } item_list_1 END [ package_name ] ;
完整語法結構見:http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/create_package.htm#LNPLS01371
說明:
package_name:包名。
invoker_rights_clause:使用誰的權限運行,格式如下:
AUTHID { CURRENT_USER | DEFINER }
item_list_1:聲明包的公用組件列表
{ type_definition -- 數據類型
| cursor_declaration -- 遊標
| item_declaration -- 變量、常量等
| function_declaration -- 函數
| procedure_declaration -- 過程
}
[ { type_definition
| cursor_declaration
| item_declaration
| function_declaration
| procedure_declaration
| pragma
}
]...
示例:
CREATE OR REPLACE PACKAGE emp_mgmt AS
-- 函數
FUNCTION hire (last_name VARCHAR2, job_id VARCHAR2,
manager_id NUMBER, salary NUMBER,
commission_pct NUMBER, department_id NUMBER)
RETURN NUMBER;
FUNCTION create_dept(department_id NUMBER, location_id NUMBER)
RETURN NUMBER;
-- 過程
PROCEDURE remove_emp(employee_id NUMBER);
PROCEDURE remove_dept(department_id NUMBER);
PROCEDURE increase_sal(employee_id NUMBER, salary_incr NUMBER);
PROCEDURE increase_comm(employee_id NUMBER, comm_incr NUMBER);
-- 異常
no_comm EXCEPTION;
no_sal EXCEPTION;
END emp_mgmt;
3.2 建立包體
語法:
CREATE [ OR REPLACE ] PACKAGE BODY [ schema. ] package_name
{ IS | AS }
BEGIN statement [ statement | pragma ]...
[ EXCEPTION exception_handler [ exception_handler ]... ]
[ initialize_section ]
END [ package_name ] ;
詳細語法結構見:http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/create_package_body.htm#LNPLS01372
示例:
CREATE OR REPLACE PACKAGE BODY emp_mgmt AS
tot_emps NUMBER;
tot_depts NUMBER;
FUNCTION hire
(last_name VARCHAR2, job_id VARCHAR2,
manager_id NUMBER, salary NUMBER,
commission_pct NUMBER, department_id NUMBER)
RETURN NUMBER IS new_empno NUMBER;
BEGIN
SELECT employees_seq.NEXTVAL
INTO new_empno
FROM DUAL;
INSERT INTO employees
VALUES (new_empno, 'First', 'Last','[email protected]',
'(415)555-0100','18-JUN-02','IT_PROG',90000000,00,
100,110);
tot_emps := tot_emps + 1;
RETURN(new_empno);
END;
FUNCTION create_dept(department_id NUMBER, location_id NUMBER)
RETURN NUMBER IS
new_deptno NUMBER;
BEGIN
SELECT departments_seq.NEXTVAL
INTO new_deptno
FROM dual;
INSERT INTO departments
VALUES (new_deptno, 'department name', 100, 1700);
tot_depts := tot_depts + 1;
RETURN(new_deptno);
END;
PROCEDURE remove_emp (employee_id NUMBER) IS
BEGIN
DELETE FROM employees
WHERE employees.employee_id = remove_emp.employee_id;
tot_emps := tot_emps - 1;
END;
PROCEDURE remove_dept(department_id NUMBER) IS
BEGIN
DELETE FROM departments
WHERE departments.department_id = remove_dept.department_id;
tot_depts := tot_depts - 1;
SELECT COUNT(*) INTO tot_emps FROM employees;
END;
PROCEDURE increase_sal(employee_id NUMBER, salary_incr NUMBER) IS
curr_sal NUMBER;
BEGIN
SELECT salary INTO curr_sal FROM employees
WHERE employees.employee_id = increase_sal.employee_id;
IF curr_sal IS NULL
THEN RAISE no_sal;
ELSE
UPDATE employees
SET salary = salary + salary_incr
WHERE employee_id = employee_id;
END IF;
END;
PROCEDURE increase_comm(employee_id NUMBER, comm_incr NUMBER) IS
curr_comm NUMBER;
BEGIN
SELECT commission_pct
INTO curr_comm
FROM employees
WHERE employees.employee_id = increase_comm.employee_id;
IF curr_comm IS NULL
THEN RAISE no_comm;
ELSE
UPDATE employees
SET commission_pct = commission_pct + comm_incr;
END IF;
END;
END emp_mgmt;
4. 調用包的組件
包的名稱是唯一的,但對於兩個包中的公有組件的名稱可以相同,用“包名.公有組件名“加以區分。
示例:
DECLARE
new_dno NUMBER; -- 部門編號
BEGIN
-- 調用emp_mgmt包的create_dept函數創建部門:
new_dno := emp_mgmt.create_dept(85, 100);
DBMS_OUTPUT.PUT_LINE('部門編號:' || new_dno);
-- 調用emp_mgmt包的increase_sal過程爲員工加薪:
emp_mgmt.increase_sal(23, 800);
END;
5. 包中的遊標
在包中使用無參遊標,示例:
--定義包規範
CREATE OR REPLACE PACKAGE PKG_STU IS
CURSOR getStuInfo RETURN stuInfo%ROWTYPE;
END PKG_STU;
--定義包體
CREATE OR REPLACE PACKAGE BODY PKG_STU AS
CURSOR getStuInfo RETURN stuInfo%ROWTYPE IS
SELECT * FROM stuInfo;
END PKG_STU;
--調用包組件
BEGIN
FOR stu_Record IN PKG_STU.getStuInfo LOOP
DBMS_OUTPUT.PUT_LINE('學員姓名:'||stu_Record.name||',學號:'||stu_Record.id||',年齡:'||stu_Record.age);
END LOOP;
END;
在包中使用有參數的遊標,示例:
--定義包規範
CREATE OR REPLACE PACKAGE PKG_STU IS
CURSOR getStuInfo(studentNo VARCHAR2) RETURN stuInfo%ROWTYPE;
END PKG_STU;
--定義包體
CREATE OR REPLACE PACKAGE BODY PKG_STU AS
CURSOR getStuInfo(studentNo VARCHAR2) RETURN stuInfo%ROWTYPE IS
SELECT * FROM stuInfo WHERE id=studentNo;
END PKG_STU;
--調用包組件
BEGIN
FOR stu_Record IN PKG_STU.getStuInfo(2) LOOP
DBMS_OUTPUT.PUT_LINE('學員姓名:'||stu_Record.name||',學號:'||stu_Record.id||',年齡:'||stu_Record.age);
END LOOP;
END;
由於遊標變量是一個指針,其狀態是不確定的,因此它不能隨同包存儲在數據庫中,即不能在PL/SQL包中聲明遊標變量。但在包中可以創建遊標變量參照類型,並可向包中的子程序傳遞遊標變量參數。
示例:
-- 創建包規範
CREATE OR REPLACE PACKAGE CURROR_VARIBAL_PKG AS
TYPE dept_cur_type IS REF CURSOR RETURN dept%ROWTYPE; --強類型
TYPE cur_type IS REF CURSOR;-- 弱類型
PROCEDURE proc_open_dept_var(
dept_cur IN OUT dept_cur_type,
choice INTEGER DEFAULT 0,
dept_no NUMBER DEFAULT 50,
dept_name VARCHAR DEFAULT '%');
END;
-- 創建包體
CREATE OR REPLACE PACKAGE BODY CURROR_VARIBAL_PKG
AS
PROCEDURE proc_open_dept_var(
dept_cur IN OUT dept_cur_type,
choice INTEGER DEFAULT 0,
dept_no NUMBER DEFAULT 50,
dept_name VARCHAR DEFAULT '%')
IS
BEGIN
IF choice = 1 THEN
OPEN dept_cur FOR SELECT * FROM dept WHERE deptno = dept_no;
ELSIF choice = 2 THEN
OPEN dept_cur FOR SELECT * FROM dept WHERE dname LIKE dept_name;
ELSE
OPEN dept_cur FOR SELECT * FROM dept;
END IF;
END proc_open_dept_var;
END CURROR_VARIBAL_PKG;
定義一個過程,打開弱類型的遊標變量:
--定義過程
CREATE OR REPLACE PROCEDURE proc_open_cur_type(
cur IN OUT CURROR_VARIBAL_PKG.cur_type,
first_cap_in_table_name CHAR)
AS
BEGIN
IF first_cap_in_table_name = 'D' THEN
OPEN cur FOR SELECT * FROM dept;
ELSE
OPEN cur FOR SELECT * FROM emp;
END IF;
END proc_open_cur_type;
測試包中游標變量類型的使用:
DECLARE
dept_rec Dept%ROWTYPE;
emp_rec Emp%ROWTYPE;
dept_cur CURROR_VARIBAL_PKG.dept_cur_type;
cur CURROR_VARIBAL_PKG.cur_type;
BEGIN
DBMS_OUTPUT.PUT_LINE('遊標變量強類型:');
CURROR_VARIBAL_PKG.proc_open_dept_var(dept_cur, 1, 30);
FETCH dept_cur INTO dept_rec;
WHILE dept_cur%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(dept_rec.deptno||':'||dept_rec.dname);
FETCH dept_cur INTO dept_rec;
END LOOP;
CLOSE dept_cur;
DBMS_OUTPUT.PUT_LINE('遊標變量弱類型:');
CURROR_VARIBAL_PKG.proc_open_dept_var(cur, 2, dept_name => 'A%');
FETCH cur INTO dept_rec;
WHILE cur%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(dept_rec.deptno||':'||dept_rec.dname);
FETCH cur INTO dept_rec;
END LOOP;
DBMS_OUTPUT.PUT_LINE('遊標變量弱類型—dept表:');
proc_open_cur_type(cur, 'D');
FETCH cur INTO dept_rec;
WHILE cur%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(dept_rec.deptno||':'||dept_rec.dname);
FETCH cur INTO dept_rec;
END LOOP;
DBMS_OUTPUT.PUT_LINE('遊標變量弱類型—emp表:');
proc_open_cur_type(cur, 'E');
FETCH cur INTO emp_rec;
WHILE cur%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(emp_rec.empno||':'||emp_rec.ename);
FETCH cur INTO emp_rec;
END LOOP;
CLOSE cur;
END;
6. 子程序重載
所謂重載時指兩個或多個子程序有相同的名稱,但擁有不同的參數變量、參數順序或參數數據類型。
在調用重載子程序時,主程序將根據實際參數的類型和數目,自動確定調用哪個子程序。
PL/SQL允許對包內子程序和本地子程序進行重載。
示例:
-- 定義包規範
CREATE OR REPLACE PACKAGE PKG_EMP AS
FUNCTION get_salary(eno NUMBER) RETURN NUMBER;
FUNCTION get_salary(empname VARCHAR2) RETURN NUMBER;
END PKG_EMP;
-- 定義包體
CREATE OR REPLACE PACKAGE BODY PKG_EMP AS
FUNCTION get_salary(eno NUMBER) RETURN NUMBER
IS
v_salary NUMBER(10, 4);
BEGIN
SELECT sal INTO v_salary FROM emp WHERE empno=eno;
RETURN v_salary;
END;
FUNCTION get_salary(empname VARCHAR2) RETURN NUMBER
IS
v_salary NUMBER(10, 4);
BEGIN
SELECT sal INTO v_salary FROM emp WHERE ename=empname;
RETURN v_salary;
END;
END PKG_EMP;
測試:
DECLARE
v_sal NUMBER(10, 4);
BEGIN
v_sal := PKG_EMP.get_salary(7499);
DBMS_OUTPUT.PUT_LINE('工資:' || v_sal);
v_sal := PKG_EMP.get_salary('MARTIN');
DBMS_OUTPUT.PUT_LINE('工資:' || v_sal);
END;