PL/SQL 程序設計簡介程序員
SQL語言只是訪問、操做數據庫的語言,並非一種具備流程控制的程序設計語言,而只有程序設計語言才能用於應用軟件的開發。PL /SQL是一種高級數據庫程序設計語言,該語言專門用於在各類環境下對ORACLE數據庫進行訪問。因爲該語言集成於數據庫服務器中,因此PL/SQL代碼能夠對數據進行快速高效的處理。除此以外,能夠在ORACLE數據庫的某些客戶端工具中,使用PL/SQL語言也是該語言的一個特色。算法
1.1 SQL與PL/SQLsql
1.1.1 什麼是PL/SQL?數據庫
PL/SQL是 Procedure Language & Structured Query Language 的縮寫。ORACLE的SQL是支持ANSI(American national Standards Institute)和ISO92 (International Standards Organization)標準的產品。PL/SQL是對SQL語言存儲過程語言的擴展。從ORACLE6之後,ORACLE的RDBMS附帶了PL/SQL。它如今已經成爲一種過程處理語言,簡稱PL/SQL。目前的PL/SQL包括兩部分,一部分是數據庫引擎部分;另外一部分是可嵌入到許多產品(如C語言,JAVA語言等)工具中的獨立引擎。能夠將這兩部分稱爲:數據庫PL/SQL和工具PL/SQL。二者的編程很是類似。都具備編程結構、語法和邏輯機制。工具PL/SQL另外還增長了用於支持工具(如ORACLE Forms)的句法,如:在窗體上設置按鈕等。本章主要介紹數據庫PL/SQL內容。express
1.2 PL/SQL的優勢或特徵編程
1.2.1 有利於客戶/服務器環境應用的運行c#
對於客戶/服務器環境來講,真正的瓶頸是網絡上。不管網絡多快,只要客戶端與服務器進行大量的數據交換。應用運行的效率天然就回受到影響。若是使用PL/SQL進行編程,將這種具備大量數據處理的應用放在服務器端來執行。天然就省去了數據在網上的傳輸時間。數組
1.2.2 適合於客戶環境安全
PL/SQL因爲分爲數據庫PL/SQL部分和工具PL/SQL。對於客戶端來講,PL/SQL能夠嵌套到相應的工具中,客戶端程序能夠執行本地包含PL/SQL部分,也能夠向服務發SQL命令或激活服務器端的PL/SQL程序運行。服務器
1.2.3 過程化
PL/SQL是Oracle在標準SQL上的過程性擴展,不只容許在PL/SQL程序內嵌入SQL語句,並且容許使用各類類型的條件分支語句和循環語句,能夠多個應用程序之間共享其解決方案。
1.2.4 模塊化
PL/SQL程序結構是一種描述性很強、界限分明的塊結構、嵌套塊結構,被分紅單獨的過程、函數、觸發器,且能夠把它們組合爲程序包,提升程序的模塊化能力。
1.2.5 運行錯誤的可處理性
使用PL/SQL提供的異常處理(EXCEPTION),開發人員可集中處理各類ORACLE錯誤和PL/SQL錯誤,或處理系統錯誤與自定義錯誤,以加強應用程序的健壯性。
1.2.6 提供大量內置程序包
ORACLE提供了大量的內置程序包。經過這些程序包可以實現DBS的一些低層操做、高級功能,不論對DBA仍是應用開發人員都具備重要做用。
固然還有其它的一些優勢如:更好的性能、可移植性和兼容性、可維護性、易用性與快速性等。
1.3 PL/SQL 可用的SQL語句
PL/SQL是ORACLE系統的核心語言,如今ORACLE的許多部件都是由PL/SQL寫成。在PL/SQL中可使用的SQL語句有:
INSERT,UPDATE,DELETE,SELECT INTO,COMMIT,ROLLBACK,SAVEPOINT。
提示:在 PL/SQL中只能用 SQL語句中的 DML 部分,不能用 DDL 部分,若是要在PL/SQL中使用DDL(如CREATE table 等)的話,只能以動態的方式來使用。
l ORACLE 的 PL/SQL 組件在對 PL/SQL 程序進行解釋時,同時對在其所使用的表名、列名及數據類型進行檢查。
l PL/SQL 能夠在SQL*PLUS 中使用。
l PL/SQL 能夠在高級語言中使用。
l PL/SQL能夠在ORACLE的開發工具中使用(如:SQL Developer或Procedure Builder等)。
l 其它開發工具也能夠調用PL/SQL編寫的過程和函數,如Power Builder 等均可以調用服務器端的PL/SQL過程。
1.4 運行PL/SQL程序
PL/SQL程序的運行是經過ORACLE中的一個引擎來進行的。這個引擎可能在ORACLE的服務器端,也可能在ORACLE 應用開發的客戶端。引擎執行PL/SQL中的過程性語句,而後將SQL語句發送給數據庫服務器來執行。再將結果返回給執行端。
PL/SQL程序由三個塊組成,即聲明部分、執行部分、異常處理部分。
PL/SQL塊的結構以下:
DECLARE
-- 聲明部分: 在此聲明PL/SQL用到的變量,類型及遊標,以及局部的存儲過程和函數
BEGIN
-- 執行部分: 過程及SQL 語句 , 即程序的主要部分
EXCEPTION
-- 執行異常部分: 錯誤處理
END;
其中:執行部分不能省略。
PL/SQL塊能夠分爲三類:
1. 無名塊或匿名塊(anonymous):動態構造,只能執行一次,可調用其它程序,但不能被其它程序調用。
2. 命名塊(named):是帶有名稱的匿名塊,這個名稱就是標籤。
3. 子程序(subprogram):存儲在數據庫中的存儲過程、函數等。當在數據庫上創建好後能夠在其它程序中調用它們。
4. 觸發器 (Trigger):當數據庫發生操做時,會觸發一些事件,從而自動執行相應的程序。
5. 程序包(package):存儲在數據庫中的一組子程序、變量定義。在包中的子程序能夠被其它程序包或子程序調用。但若是聲明的是局部子程序,則只能在定義該局部子程序的塊中調用該局部子程序。
2.2 PL/SQL結構
l PL/SQL塊中能夠包含子塊;
l 子塊能夠位於 PL/SQL中的任何部分;
l 子塊也即PL/SQL中的一條命令;
2.3 標識符
PL/SQL程序設計中的標識符定義與SQL 的標識符定義的要求相同。要求和限制有:
l 標識符名不能超過30字符;
l 第一個字符必須爲字母;
l 不分大小寫;
l 不能用’-‘(減號);
l 不能是SQL保留字。
提示: 通常不要把變量名聲明與表中字段名徹底同樣,若是這樣可能獲得不正確的結果.
例如:下面的例子將會刪除全部的紀錄,而不是’EricHu’的記錄;
DECLARE
ename varchar2( 20) : = ' EricHu ';
BEGIN
DELETE FROM scott.emp WHERE ename =ename;
END;
變量命名在PL/SQL中有特別的講究,建議在系統的設計階段就要求全部編程人員共同遵照必定的要求,使得整個系統的文檔在規範上達到要求。下面是建議的命名方法:
標識符 |
命名規則 |
例子 |
程序變量 |
V_name |
V_name |
程序常量 |
C_Name |
C_company_name |
遊標變量 |
Cursor_Name |
Cursor_Emp |
異常標識 |
E_name |
E_too_many |
表類型 |
Name_table_type |
Emp_record_type |
表 |
Name_table |
Emp |
記錄類型 |
Name_record |
Emp_record |
SQL*Plus 替代變量 |
P_name |
P_sal |
綁定變量 |
G_name |
G_year_sal |
2.4 PL/SQL 變量類型
在前面的介紹中,有系統的數據類型,也能夠自定義數據類型。下表給出ORACLE類型和PL/SQL中的變量類型的合法使用列表:
2.4.1 變量類型
在ORACLE8i中可使用的變量類型有:
類型 |
子類 |
說 明 |
範 圍 |
ORACLE限制 |
CHAR |
Character String Rowid Nchar |
定長字符串
民族語言字符集 |
0à32767 可選,確省=1 |
2000 |
VARCHAR2 |
Varchar, String NVARCHAR2 |
可變字符串 民族語言字符集 |
0à32767 4000 |
4000 |
BINARY_INTEGER |
|
帶符號整數,爲整數計算優化性能 |
|
|
NUMBER(p,s) |
Dec
Double precision Integer Int Numeric Real Small int |
小數, NUMBER 的子類型 高精度實數 整數, NUMBER 的子類型 整數, NUMBER 的子類型 與NUMBER等價 與NUMBER等價 整數, 比 integer 小 |
|
|
LONG |
|
變長字符串 |
0->2147483647 |
32,767字節 |
DATE |
|
日期型 |
公元前4712年1月1日至公元后4712年12月31日 |
|
BOOLEAN |
|
布爾型 |
TRUE, FALSE,NULL |
不使用 |
ROWID |
|
存放數據庫行號 |
|
|
UROWID |
|
通用行標識符,字符類型 |
|
|
|
|
|
|
|
例1. 插入一條記錄並顯示;
DECLARE
Row_id ROWID;
info VARCHAR2( 40);
BEGIN
INSERT INTO scott.dept VALUES ( 90, ' 財務室 ', ' 海口 ')
RETURNING rowid, dname || ' : ' ||to_char(deptno) || ' : ' ||loc
INTO row_id, info;
DBMS_OUTPUT.PUT_LINE( ' ROWID: ' ||row_id);
DBMS_OUTPUT.PUT_LINE(info);
END;
其中:
RETURNING子句用於檢索INSERT語句中所影響的數據行數,當INSERT語句使用VALUES 子句插入數據時,RETURNING 字句還可將列表達式、ROWID和REF值返回到輸出變量中。在使用RETURNING 子句是應注意如下幾點限制:
1.不能與DML語句和遠程對象一塊兒使用;
2.不能檢索LONG 類型信息;
3.當經過視圖向基表中插入數據時,只能與單基表視圖一塊兒使用。
例2. 修改一條記錄並顯示
DECLARE
Row_id ROWID;
info VARCHAR2( 40);
BEGIN
UPDATE dept SET deptno = 100 WHERE DNAME = ' 財務室 '
RETURNING rowid, dname || ' : ' ||to_char(deptno) || ' : ' ||loc
INTO row_id, info;
DBMS_OUTPUT.PUT_LINE( ' ROWID: ' ||row_id);
DBMS_OUTPUT.PUT_LINE(info);
END;
其中:
RETURNING子句用於檢索被修改行的信息。當UPDATE語句修改單行數據時,RETURNING 子句能夠檢索被修改行的ROWID和REF值,以及行中被修改列的列表達式,並可將他們存儲到PL/SQL變量或複合變量中;當UPDATE語句修改多行數據時,RETURNING 子句能夠將被修改行的ROWID和REF值,以及列表達式值返回到複合變量數組中。在UPDATE中使用RETURNING 子句的限制與INSERT語句中對RETURNING子句的限制相同。
例3. 刪除一條記錄並顯示
DECLARE
Row_id ROWID;
info VARCHAR2( 40);
BEGIN
DELETE dept WHERE DNAME = ' 辦公室 '
RETURNING rowid, dname || ' : ' ||to_char(deptno) || ' : ' ||loc
INTO row_id, info;
DBMS_OUTPUT.PUT_LINE( ' ROWID: ' ||row_id);
DBMS_OUTPUT.PUT_LINE(info);
END;
其中:
RETURNING子句用於檢索被刪除行的信息:當DELETE語句刪除單行數據時,RETURNING 子句能夠檢索被刪除行的ROWID和REF值,以及被刪除列的列表達式,並可將他們存儲到PL/SQL變量或複合變量中;當DELETE語句刪除多行數據時,RETURNING 子句能夠將被刪除行的ROWID和REF值,以及列表達式值返回到複合變量數組中。在DELETE中使用RETURNING 子句的限制與INSERT語句中對RETURNING子句的限制相同。
2.4.2 複合類型
ORACLE 在 PL/SQL 中除了提供象前面介紹的各類類型外,還提供一種稱爲複合類型的類型---記錄和表.
2.4.2.1 記錄類型
記錄類型相似於C語言中的結構數據類型,它把邏輯相關的、分離的、基本數據類型的變量組成一個總體存儲起來,它必須包括至少一個標量型或RECORD 數據類型的成員,稱做PL/SQL RECORD 的域(FIELD),其做用是存放互不相同但邏輯相關的信息。在使用記錄數據類型變量時,須要先在聲明部分先定義記錄的組成、記錄的變量,而後在執行部分引用該記錄變量自己或其中的成員。
定義記錄類型語法以下:
TYPE record_name IS RECORD(
v1 data_type1 [ NOT NULL ] [ := default_value ],
v2 data_type2 [ NOT NULL ] [ := default_value ],
......
vn data_typen [ NOT NULL ] [ := default_value ] );
例4 :
DECLARE
TYPE test_rec IS RECORD(
Name VARCHAR2( 30) NOT NULL : = ' 胡勇 ',
Info VARCHAR2( 100));
rec_book test_rec;
BEGIN
rec_book.Name : = ' 胡勇 ';
rec_book.Info : = ' 談PL/SQL編程; ';
DBMS_OUTPUT.PUT_LINE(rec_book.Name || ' ' ||rec_book.Info);
END;
能夠用 SELECT語句對記錄變量進行賦值,只要保證記錄字段與查詢結果列表中的字段相配便可。
例5 :
DECLARE
-- 定義與hr.employees表中的這幾個列相同的記錄數據類型
TYPE RECORD_TYPE_EMPLOYEES IS RECORD(
f_name hr.employees.first_name %TYPE,
h_date hr.employees.hire_date %TYPE,
j_id hr.employees.job_id %TYPE);
-- 聲明一個該記錄數據類型的記錄變量
v_emp_record RECORD_TYPE_EMPLOYEES;
BEGIN
SELECT first_name, hire_date, job_id INTO v_emp_record
FROM employees
WHERE employee_id = &emp_id;
DBMS_OUTPUT.PUT_LINE( ' 僱員名稱: ' ||v_emp_record.f_name
|| ' 僱傭日期: ' ||v_emp_record.h_date
|| ' 崗位: ' ||v_emp_record.j_id);
END;
一個記錄類型的變量只能保存從數據庫中查詢出的一行記錄,若查詢出了多行記錄,就會出現錯誤。
2.4.2.2 數組類型
數據是具備相同數據類型的一組成員的集合。每一個成員都有一個惟一的下標,它取決於成員在數組中的位置。在PL/SQL中,數組數據類型是VARRAY。
定義VARRY數據類型語法以下:
TYPE varray_name IS VARRAY(size) OF element_type [ NOT NULL ];
varray_name是VARRAY數據類型的名稱,size是下整數,表示可容納的成員的最大數量,每一個成員的數據類型是element_type。默認成員能夠取空值,不然須要使用NOT NULL加以限制。對於VARRAY數據類型來講,必須通過三個步驟,分別是:定義、聲明、初始化。
例6 :
DECLARE
-- 定義一個最多保存5個VARCHAR(25)數據類型成員的VARRAY數據類型
TYPE reg_varray_type IS VARRAY( 5) OF VARCHAR( 25);
-- 聲明一個該VARRAY數據類型的變量
v_reg_varray REG_VARRAY_TYPE;
BEGIN
-- 用構造函數語法賦予初值
v_reg_varray : = reg_varray_type
( ' 中國 ', ' 美國 ', ' 英國 ', ' 日本 ', ' 法國 ');
DBMS_OUTPUT.PUT_LINE( ' 地區名稱: ' ||v_reg_varray( 1) || ' 、 '
||v_reg_varray( 2) || ' 、 '
||v_reg_varray( 3) || ' 、 '
||v_reg_varray( 4));
DBMS_OUTPUT.PUT_LINE( ' 賦予初值NULL的第5個成員的值: ' ||v_reg_varray( 5));
-- 用構造函數語法賦予初值後就能夠這樣對成員賦值
v_reg_varray( 5) : = ' 法國 ';
DBMS_OUTPUT.PUT_LINE( ' 第5個成員的值: ' ||v_reg_varray( 5));
END;
2.4.2.3 使用%TYPE
定義一個變量,其數據類型與已經定義的某個數據變量(尤爲是表的某一列)的數據類型相一致,這時可使用%TYPE。
使用%TYPE特性的優勢在於:
l 所引用的數據庫列的數據類型能夠沒必要知道;
l 所引用的數據庫列的數據類型能夠實時改變,容易保持一致,也不用修改PL/SQL程序。
例7:
DECLARE
-- 用%TYPE 類型定義與表相配的字段
TYPE T_Record IS RECORD(
T_no emp.empno %TYPE,
T_name emp.ename %TYPE,
T_sal emp.sal %TYPE );
-- 聲明接收數據的變量
v_emp T_Record;
BEGIN
SELECT empno, ename, sal INTO v_emp FROM emp WHERE empno = 7788;
DBMS_OUTPUT.PUT_LINE
(TO_CHAR(v_emp.t_no) || ' ' ||v_emp.t_name || ' ' || TO_CHAR(v_emp.t_sal));
END;
例8:
DECLARE
v_empno emp.empno %TYPE : =&no;
Type t_record is record (
v_name emp.ename %TYPE,
v_sal emp.sal %TYPE,
v_date emp.hiredate %TYPE);
Rec t_record;
BEGIN
SELECT ename, sal, hiredate INTO Rec FROM emp WHERE empno =v_empno;
DBMS_OUTPUT.PUT_LINE(Rec.v_name || ' --- ' ||Rec.v_sal || ' -- ' ||Rec.v_date);
END;
2.4.3 使用%ROWTYPE
PL/SQL 提供%ROWTYPE操做符, 返回一個記錄類型, 其數據類型和數據庫表的數據結構相一致。
使用%ROWTYPE特性的優勢在於:
l 所引用的數據庫中列的個數和數據類型能夠沒必要知道;
l 所引用的數據庫中列的個數和數據類型能夠實時改變,容易保持一致,也不用修改PL/SQL程序。
例9:
DECLARE
v_empno emp.empno %TYPE : =&no;
rec emp %ROWTYPE;
BEGIN
SELECT * INTO rec FROM emp WHERE empno =v_empno;
DBMS_OUTPUT.PUT_LINE( ' 姓名: ' ||rec.ename || ' 工資: ' ||rec.sal || ' 工做時間: ' ||rec.hiredate);
END;
2.4.4 LOB類型
ORACLE提供了LOB (Large OBject)類型,用於存儲大的數據對象的類型。ORACLE目前主要支持BFILE, BLOB, CLOB 及 NCLOB 類型。
BFILE (Movie)
存放大的二進制數據對象,這些數據文件不放在數據庫裏,而是放在操做系統的某個目錄裏,數據庫的表裏只存放文件的目錄。
BLOB(Photo)
存儲大的二進制數據類型。變量存儲大的二進制對象的位置。大二進制對象的大小<=4GB。
CLOB(Book)
存儲大的字符數據類型。每一個變量存儲大字符對象的位置,該位置指到大字符數據塊。大字符對象的大小<=4GB。
NCLOB
存儲大的NCHAR字符數據類型。每一個變量存儲大字符對象的位置,該位置指到大字符數據塊。大字符對象的大小<=4GB。
2.4.5 BIND 變量
綁定變量是在主機環境中定義的變量。在PL/SQL 程序中可使用綁定變量做爲他們將要使用的其它變量。爲了在PL/SQL 環境中聲明綁定變量,使用命令VARIABLE。例如:
VARIABLE return_code NUMBER
VARIABLE return_msg VARCHAR2( 20)
能夠經過SQL*Plus命令中的PRINT 顯示綁定變量的值。例如:
PRINT return_code
PRINT return_msg
例10:
VARIABLE result NUMBER;
BEGIN
SELECT (sal * 10) +nvl(comm, 0) INTO :result FROM emp
WHERE empno = 7844;
END;
-- 而後再執行
PRINT result
2.4.6 PL/SQL 表(TABLE)
定義記錄表(或索引表)數據類型。它與記錄類型類似,但它是對記錄類型的擴展。它能夠處理多行記錄,相似於高級中的二維數組,使得能夠在PL/SQL中模仿數據庫中的表。
定義記錄表類型的語法以下:
TYPE table_name IS TABLE OF element_type [ NOT NULL ]
INDEX BY [ BINARY_INTEGER | PLS_INTEGER | VARRAY2 ];
關鍵字INDEX BY表示建立一個主鍵索引,以便引用記錄表變量中的特定行。
方法 |
描述 |
EXISTS(n) |
若是集合的第n個成員存在,則返回true |
COUNT |
返回已經分配了存儲空間即賦值了的成員數量 |
FIRST LAST |
FIRST:返回成員的最低下標值 LAST: 返回成員的最高下標值 |
PRIOR(n) |
返回下標爲n的成員的前一個成員的下標。若是沒有則返回NULL |
NEXT(N) |
返回下標爲n的成員的後一個成員的下標。若是沒有則返回NULL |
TRIM |
TRIM:刪除末尾一個成員 TRIM(n) :刪除末尾n個成員 |
DELETE |
DELETE:刪除全部成員 DELETE(n) :刪除第n個成員 DELETE(m, n) :刪除從n到m的成員 |
EXTEND |
EXTEND:添加一個null成員 EXTEND(n):添加n個null成員 EXTEND(n,i):添加n個成員,其值與第i個成員相同 |
LIMIT |
返回在varray類型變量中出現的最高下標值 |
例11:
DECLARE
TYPE dept_table_type IS TABLE OF
dept %ROWTYPE INDEX BY BINARY_INTEGER;
my_dname_table dept_table_type;
v_count number( 2) : = 4;
BEGIN
FOR int IN 1 .. v_count LOOP
SELECT * INTO my_dname_table( int) FROM dept WHERE deptno = int * 10;
END LOOP;
FOR int IN my_dname_table.FIRST .. my_dname_table.LAST LOOP
DBMS_OUTPUT.PUT_LINE( ' Department number: ' ||my_dname_table( int).deptno);
DBMS_OUTPUT.PUT_LINE( ' Department name: ' || my_dname_table( int).dname);
END LOOP;
END;
例12:按一維數組使用記錄表
DECLARE
-- 定義記錄表數據類型
TYPE reg_table_type IS TABLE OF varchar2( 25)
INDEX BY BINARY_INTEGER;
-- 聲明記錄表數據類型的變量
v_reg_table REG_TABLE_TYPE;
BEGIN
v_reg_table( 1) : = ' Europe ';
v_reg_table( 2) : = ' Americas ';
v_reg_table( 3) : = ' Asia ';
v_reg_table( 4) : = ' Middle East and Africa ';
v_reg_table( 5) : = ' NULL ';
DBMS_OUTPUT.PUT_LINE( ' 地區名稱: ' ||v_reg_table ( 1) || ' 、 '
||v_reg_table ( 2) || ' 、 '
||v_reg_table ( 3) || ' 、 '
||v_reg_table ( 4));
DBMS_OUTPUT.PUT_LINE( ' 第5個成員的值: ' ||v_reg_table( 5));
END;
例13:按二維數組使用記錄表
DECLARE
-- 定義記錄表數據類型
TYPE emp_table_type IS TABLE OF employees %ROWTYPE
INDEX BY BINARY_INTEGER;
-- 聲明記錄表數據類型的變量
v_emp_table EMP_TABLE_TYPE;
BEGIN
SELECT first_name, hire_date, job_id INTO
v_emp_table( 1).first_name,v_emp_table( 1).hire_date, v_emp_table( 1).job_id
FROM employees WHERE employee_id = 177;
SELECT first_name, hire_date, job_id INTO
v_emp_table( 2).first_name,v_emp_table( 2).hire_date, v_emp_table( 2).job_id
FROM employees WHERE employee_id = 178;
DBMS_OUTPUT.PUT_LINE( ' 177僱員名稱: ' ||v_emp_table( 1).first_name
|| ' 僱傭日期: ' ||v_emp_table( 1).hire_date
|| ' 崗位: ' ||v_emp_table( 1).job_id);
DBMS_OUTPUT.PUT_LINE( ' 178僱員名稱: ' ||v_emp_table( 2).first_name
|| ' 僱傭日期: ' ||v_emp_table( 2).hire_date
|| ' 崗位: ' ||v_emp_table( 2).job_id);
END;
2.5 運算符和表達式(數據定義)
2.5.1 關係運算符
運算符 |
意義 |
= |
等於 |
<> , != , ~= , ^= |
不等於 |
< |
小於 |
> |
大於 |
<= |
小於或等於 |
>= |
大於或等於 |
2.5.2 通常運算符
運算符 |
意義 |
+ |
加號 |
- |
減號 |
* |
乘號 |
/ |
除號 |
:= |
賦值號 |
=> |
關係號 |
.. |
範圍運算符 |
|| |
字符鏈接符 |
2.5.3 邏輯運算符
運算符 |
意義 |
IS NULL |
是空值 |
BETWEEN AND |
介於二者之間 |
IN |
在一列值中間 |
AND |
邏輯與 |
OR |
邏輯或 |
NOT |
取返,如IS NOT NULL, NOT IN |
2.6 變量賦值
在PL/SQL編程中,變量賦值是一個值得注意的地方,它的語法以下:
variable := expression ;
variable 是一個PL/SQL變量, expression 是一個PL/SQL 表達式.
2.6.1 字符及數字運算特色
空值加數字還是空值:NULL + < 數字> = NULL
空值加(鏈接)字符,結果爲字符:NULL || <字符串> = < 字符串>
2.6.2 BOOLEAN 賦值
布爾值只有TRUE, FALSE及 NULL 三個值。如:
DECLARE
bDone BOOLEAN;
BEGIN
bDone : = FALSE;
WHILE NOT bDone LOOP
Null;
END LOOP;
END;
2.6.3 數據庫賦值
數據庫賦值是經過 SELECT語句來完成的,每次執行 SELECT語句就賦值一次,通常要求被賦值的變量與SELECT中的列名要一一對應。如:
例14:
DECLARE
emp_id emp.empno %TYPE : = 7788;
emp_name emp.ename %TYPE;
wages emp.sal %TYPE;
BEGIN
SELECT ename, NVL(sal, 0) + NVL(comm, 0) INTO emp_name, wages
FROM emp WHERE empno = emp_id;
DBMS_OUTPUT.PUT_LINE(emp_name || ' ---- ' ||to_char(wages));
END;
提示:不能將SELECT語句中的列賦值給布爾變量。
2.6.4 可轉換的類型賦值
l CHAR 轉換爲 NUMBER:
使用 TO_NUMBER 函數來完成字符到數字的轉換,如:
v_total := TO_NUMBER('100.0') + sal;
l NUMBER 轉換爲CHAR:
使用 TO_CHAR函數能夠實現數字到字符的轉換,如:
v_comm := TO_CHAR('123.45') || '元' ;
l 字符轉換爲日期:
使用 TO_DATE函數能夠實現 字符到日期的轉換,如:
v_date := TO_DATE('2001.07.03','yyyy.mm.dd');
l 日期轉換爲字符
使用 TO_CHAR函數能夠實現日期到字符的轉換,如:
v_to_day := TO_CHAR(SYSDATE, 'yyyy.mm.dd hh24:mi:ss') ;
2.7 變量做用範圍及可見性
在PL/SQL編程中,若是在變量的定義上沒有作到統一的話,可能會隱藏一些危險的錯誤,這樣的緣由主要是變量的做用範圍所致。變量的做用域是指變量的有效做用範圍,與其它高級語言相似,PL/SQL的變量做用範圍特色是:
l 變量的做用範圍是在你所引用的程序單元(塊、子程序、包)內。即從聲明變量開始到該塊的結束。
l 一個變量(標識)只能在你所引用的塊內是可見的。
l 當一個變量超出了做用範圍,PL/SQL引擎就釋放用來存放該變量的空間(由於它可能不用了)。
l 在子塊中從新定義該變量後,它的做用僅在該塊內。
例15:
DECLARE
Emess char( 80);
BEGIN
DECLARE
V1 NUMBER( 4);
BEGIN
SELECT empno INTO v1 FROM emp WHERE LOWER(job) = ' president ';
DBMS_OUTPUT.PUT_LINE(V1);
EXCEPTION
When TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE ( ' More than one president ');
END;
DECLARE
V1 NUMBER( 4);
BEGIN
SELECT empno INTO v1 FROM emp WHERE LOWER(job) = ' manager ';
EXCEPTION
When TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE ( ' More than one manager ');
END;
EXCEPTION
When others THEN
Emess: =substr(SQLERRM, 1, 80);
DBMS_OUTPUT.PUT_LINE(emess);
END;
2.8 註釋
在PL/SQL裏,可使用兩種符號來寫註釋,即:
l 使用雙 ‘-‘ ( 減號) 加註釋
PL/SQL容許用 – 來寫註釋,它的做用範圍是只能在一行有效。如:
V_Sal NUMBER(12,2); -- 人員的工資變量。
l 使用 /* */ 來加一行或多行註釋,如:
/***********************************************/
/* 文件名: department_salary.sql */
/* 做 者: EricHu */
/* 時 間: 2011-5-9 */
/***********************************************/
提示:被解釋後存放在數據庫中的 PL/SQL 程序,通常系統自動將程序頭部的註釋去掉。只有在 PROCEDURE 以後的註釋才被保留;另外程序中的空行也自動被去掉。
2.9 簡單例子
2.9.1 簡單數據插入例子
例16:
/* ********************************************* */
/* 文件名: test.sql */
/* 說 明:
一個簡單的插入測試,無實際應用。 */
/* 做 者: EricHu */
/* 時 間: 2011-5-9 */
/* ********************************************* */
DECLARE
v_ename VARCHAR2( 20) : = ' Bill ';
v_sal NUMBER( 7, 2) : = 1234.56;
v_deptno NUMBER( 2) : = 10;
v_empno NUMBER( 4) : = 8888;
BEGIN
INSERT INTO emp ( empno, ename, JOB, sal, deptno , hiredate )
VALUES (v_empno, v_ename, ' Manager ', v_sal, v_deptno,
TO_DATE( ' 1954.06.09 ', ' yyyy.mm.dd ') );
COMMIT;
END;
2.9.2 簡單數據刪除例子
例17:
/* ********************************************* */
/* 文件名: test_deletedata.sql */
/* 說 明:
簡單的刪除例子,不是實際應用。 */
/* 做 者: EricHu */
/* 時 間: 2011-5-9 */
/* ********************************************* */
DECLARE
v_ename VARCHAR2( 20) : = ' Bill ';
v_sal NUMBER( 7, 2) : = 1234.56;
v_deptno NUMBER( 2) : = 10;
v_empno NUMBER( 4) : = 8888;
BEGIN
INSERT INTO emp ( empno, ename, JOB, sal, deptno , hiredate )
VALUES ( v_empno, v_ename, ‘Manager’, v_sal, v_deptno,
TO_DATE(’ 1954.06. 09’,’yyyy.mm.dd’) );
COMMIT;
END;
DECLARE
v_empno number( 4) : = 8888;
BEGIN
DELETE FROM emp WHERE empno =v_empno;
COMMIT;
END;
介紹PL/SQL的流程控制語句, 包括以下三類:
l 控制語句: IF 語句
l 循環語句: LOOP語句, EXIT語句
l 順序語句: GOTO語句, NULL語句
3.1 條件語句
IF <布爾表達式 > THEN
PL /SQL 和 SQL語句
END IF;
-- ---------------------
IF <布爾表達式 > THEN
PL /SQL 和 SQL語句
ELSE
其它語句
END IF;
-- ---------------------
IF <布爾表達式 > THEN
PL /SQL 和 SQL語句
ELSIF < 其它布爾表達式 > THEN
其它語句
ELSIF < 其它布爾表達式 > THEN
其它語句
ELSE
其它語句
END IF;
提示: ELSIF 不能寫成 ELSEIF
例1:
DECLARE
v_empno employees.employee_id %TYPE : =&empno;
V_salary employees.salary %TYPE;
V_comment VARCHAR2( 35);
BEGIN
SELECT salary INTO v_salary FROM employees
WHERE employee_id = v_empno;
IF v_salary < 1500 THEN
V_comment: = ' 太少了,加點吧~! ';
ELSIF v_salary < 3000 THEN
V_comment: = ' 多了點,少點吧~! ';
ELSE
V_comment: = ' 沒有薪水~! ';
END IF;
DBMS_OUTPUT.PUT_LINE(V_comment);
exception
when no_data_found then
DBMS_OUTPUT.PUT_LINE( ' 沒有數據~! ');
when others then
DBMS_OUTPUT.PUT_LINE(sqlcode || ' --- ' || sqlerrm);
END;
例2:
DECLARE
v_first_name VARCHAR2( 20);
v_salary NUMBER( 7, 2);
BEGIN
SELECT first_name, salary INTO v_first_name, v_salary FROM employees
WHERE employee_id = &emp_id;
DBMS_OUTPUT.PUT_LINE(v_first_name || ' 僱員的工資是 ' ||v_salary);
IF v_salary < 10000 THEN
DBMS_OUTPUT.PUT_LINE( ' 工資低於10000 ');
ELSE
IF 10000 <= v_salary AND v_salary < 20000 THEN
DBMS_OUTPUT.PUT_LINE( ' 工資在10000到20000之間 ');
ELSE
DBMS_OUTPUT.PUT_LINE( ' 工資高於20000 ');
END IF;
END IF;
END;
例3:
DECLARE
v_first_name VARCHAR2( 20);
v_hire_date DATE;
v_bonus NUMBER( 6, 2);
BEGIN
SELECT first_name, hire_date INTO v_first_name, v_hire_date FROM employees
WHERE employee_id = &emp_id;
IF v_hire_date > TO_DATE( ' 01-1月-90 ') THEN
v_bonus : = 800;
ELSIF v_hire_date > TO_DATE( ' 01-1月-88 ') THEN
v_bonus : = 1600;
ELSE
v_bonus : = 2400;
END IF;
DBMS_OUTPUT.PUT_LINE(v_first_name || ' 僱員的僱傭日期是 ' ||v_hire_date
|| ' 、獎金是 ' ||v_bonus);
END;
3.2 CASE 表達式
-- -------格式一---------
CASE 條件表達式
WHEN 條件表達式結果1 THEN
語句段1
WHEN 條件表達式結果2 THEN
語句段2
......
WHEN 條件表達式結果n THEN
語句段n
[ ELSE 條件表達式結果 ]
END;
-- -------格式二---------
CASE
WHEN 條件表達式1 THEN
語句段1
WHEN 條件表達式2 THEN
語句段2
......
WHEN 條件表達式n THEN
語句段n
[ ELSE 語句段 ]
END;
例4:
DECLARE
V_grade char( 1) : = UPPER( ' &p_grade ');
V_appraisal VARCHAR2( 20);
BEGIN
V_appraisal : =
CASE v_grade
WHEN ' A ' THEN ' Excellent '
WHEN ' B ' THEN ' Very Good '
WHEN ' C ' THEN ' Good '
ELSE ' No such grade '
END;
DBMS_OUTPUT.PUT_LINE( ' Grade: ' ||v_grade || ' Appraisal: ' || v_appraisal);
END;
例5:
DECLARE
v_first_name employees.first_name %TYPE;
v_job_id employees.job_id %TYPE;
v_salary employees.salary %TYPE;
v_sal_raise NUMBER( 3, 2);
BEGIN
SELECT first_name, job_id, salary INTO
v_first_name, v_job_id, v_salary
FROM employees WHERE employee_id = &emp_id;
CASE
WHEN v_job_id = ' PU_CLERK ' THEN
IF v_salary < 3000 THEN v_sal_raise : = . 08;
ELSE v_sal_raise : = . 07;
END IF;
WHEN v_job_id = ' SH_CLERK ' THEN
IF v_salary < 4000 THEN v_sal_raise : = . 06;
ELSE v_sal_raise : = . 05;
END IF;
WHEN v_job_id = ' ST_CLERK ' THEN
IF v_salary < 3500 THEN v_sal_raise : = . 04;
ELSE v_sal_raise : = . 03;
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE( ' 該崗位不漲工資: ' ||v_job_id);
END CASE;
DBMS_OUTPUT.PUT_LINE(v_first_name || ' 的崗位是 ' ||v_job_id
|| ' 、的工資是 ' ||v_salary
|| ' 、工資漲幅是 ' ||v_sal_raise);
END;
3.3 循環
1. 簡單循環
LOOP
要執行的語句;
EXIT WHEN <條件語句 > -- 條件知足,退出循環語句
END LOOP;
例 6.
DECLARE
int NUMBER( 2) : = 0;
BEGIN
LOOP
int : = int + 1;
DBMS_OUTPUT.PUT_LINE( ' int 的當前值爲: ' || int);
EXIT WHEN int = 10;
END LOOP;
END;
2. WHILE 循環
WHILE <布爾表達式 > LOOP
要執行的語句;
END LOOP;
例7.
DECLARE
x NUMBER : = 1;
BEGIN
WHILE x <= 10 LOOP
DBMS_OUTPUT.PUT_LINE( ' X的當前值爲: ' ||x);
x: = x + 1;
END LOOP;
END;
3. 數字式循環
[ <<循環標籤>> ]
FOR 循環計數器 IN [ REVERSE ] 下限 .. 上限 LOOP
要執行的語句;
END LOOP [ 循環標籤 ];
每循環一次,循環變量自動加1;使用關鍵字REVERSE,循環變量自動減1。跟在IN REVERSE 後面的數字必須是從小到大的順序,並且必須是整數,不能是變量或表達式。可使用EXIT 退出循環。
例8.
BEGIN
FOR int in 1.. 10 LOOP
DBMS_OUTPUT.PUT_LINE( ' int 的當前值爲: ' || int);
END LOOP;
END;
例 9.
CREATE TABLE temp_table(num_col NUMBER);
DECLARE
V_counter NUMBER : = 10;
BEGIN
INSERT INTO temp_table(num_col) VALUES (v_counter );
FOR v_counter IN 20 .. 25 LOOP
INSERT INTO temp_table (num_col ) VALUES ( v_counter );
END LOOP;
INSERT INTO temp_table(num_col) VALUES (v_counter );
FOR v_counter IN REVERSE 20 .. 25 LOOP
INSERT INTO temp_table (num_col ) VALUES ( v_counter );
END LOOP;
END ;
DROP TABLE temp_table;
例10:
DECLARE
TYPE jobids_varray IS VARRAY( 12) OF VARCHAR2( 10); -- 定義一個VARRAY數據類型
v_jobids JOBIDS_VARRAY; -- 聲明一個具備JOBIDS_VARRAY數據類型的變量
v_howmany NUMBER; -- 聲明一個變量來保存僱員的數量
BEGIN
-- 用某些job_id值初始化數組
v_jobids : = jobids_varray( ' FI_ACCOUNT ', ' FI_MGR ', ' ST_CLERK ', ' ST_MAN ');
-- 用FOR...LOOP...END LOOP循環使用每一個數組成員的值
FOR i IN v_jobids.FIRST..v_jobids.LAST LOOP
-- 針對數組中的每一個崗位,決定該崗位的僱員的數量
SELECT count( *) INTO v_howmany FROM employees WHERE job_id = v_jobids(i);
DBMS_OUTPUT.PUT_LINE ( ' 崗位 ' ||v_jobids(i) ||
' 總共有 ' || TO_CHAR(v_howmany) || ' 個僱員 ');
END LOOP;
END;
例11 在While循環中嵌套loop循環
/* 求100至110之間的素數 */
DECLARE
v_m NUMBER : = 101;
v_i NUMBER;
v_n NUMBER : = 0;
BEGIN
WHILE v_m < 110 LOOP
v_i : = 2;
LOOP
IF mod(v_m, v_i) = 0 THEN
v_i : = 0;
EXIT;
END IF;
v_i : = v_i + 1;
EXIT WHEN v_i > v_m - 1;
END LOOP;
IF v_i > 0 THEN
v_n : = v_n + 1;
DBMS_OUTPUT.PUT_LINE( ' 第 ' || v_n || ' 個素數是 ' || v_m);
END IF;
v_m : = v_m + 2;
END LOOP;
END;
3.4 標號和GOTO
PL/SQL中GOTO語句是無條件跳轉到指定的標號去的意思。語法以下:
GOTO label;
......
<<label >> /* 標號是用<< >>括起來的標識符 */
注意,在如下地方使用是不合法的,編譯時會出錯誤。
u 跳轉到非執行語句前面。
u 跳轉到子塊中。
u 跳轉到循環語句中。
u 跳轉到條件語句中。
u 從異常處理部分跳轉到執行。
u 從條件語句的一部分跳轉到另外一部分。
例12:
DECLARE
V_counter NUMBER : = 1;
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE( ' V_counter的當前值爲: ' ||V_counter);
V_counter : = v_counter + 1;
IF v_counter > 10 THEN
GOTO labelOffLOOP;
END IF;
END LOOP;
<<labelOffLOOP >>
DBMS_OUTPUT.PUT_LINE( ' V_counter的當前值爲: ' ||V_counter);
END;
例13:
DECLARE
v_i NUMBER : = 0;
v_s NUMBER : = 0;
BEGIN
<<label_1 >>
v_i : = v_i + 1;
IF v_i <= 1000 THEN
v_s : = v_s + v_i;
GOTO label_1;
END IF;
DBMS_OUTPUT.PUT_LINE(v_s);
END;
3.5 NULL 語句
在PL/SQL 程序中,NULL語句是一個可執行語句,能夠用 null 語句來講明「不用作任何事情」的意思,至關於一個佔位符或不執行任何操做的空語句,可使某些語句變得有意義,提升程序的可讀性,保證其餘語句結構的完整性和正確性。如:
例14:
DECLARE
...
BEGIN
...
IF v_num IS NULL THEN
GOTO labelPrint;
END IF;
…
<<labelPrint >>
NULL; -- 不須要處理任何數據。
END;
例15:
DECLARE
v_emp_id employees.employee_id %TYPE;
v_first_name employees.first_name %TYPE;
v_salary employees.salary %TYPE;
v_sal_raise NUMBER( 3, 2);
BEGIN
v_emp_id : = &emp_id;
SELECT first_name, salary INTO v_first_name, v_salary
FROM employees WHERE employee_id = v_emp_id;
IF v_salary <= 3000 THEN
v_sal_raise : = . 10;
DBMS_OUTPUT.PUT_LINE(v_first_name || ' 的工資是 ' ||v_salary
|| ' 、工資漲幅是 ' ||v_sal_raise);
ELSE
NULL;
END IF;
END;
4.1 遊標概念
在PL/SQL塊中執行SELECT、INSERT、DELETE和UPDATE語句時,ORACLE會在內存中爲其分配上下文區(Context Area),即緩衝區。遊標是指向該區的一個指針,或是命名一個工做區(Work Area),或是一種結構化數據類型。它爲應用等量齊觀提供了一種對具備多行數據查詢結果集中的每一行數據分別進行單獨處理的方法,是設計嵌入式SQL語句的應用程序的經常使用編程方式。
在每一個用戶會話中,能夠同時打開多個遊標,其數量由數據庫初始化參數文件中的OPEN_CURSORS參數定義。
對於不一樣的SQL語句,遊標的使用狀況不一樣:
SQL語句 |
遊標 |
非查詢語句 |
隱式的 |
結果是單行的查詢語句 |
隱式的或顯示的 |
結果是多行的查詢語句 |
顯示的 |
4.1.1 處理顯式遊標
1. 顯式遊標處理
顯式遊標處理需四個 PL/SQL步驟:
l 定義/聲明遊標:就是定義一個遊標名,以及與其相對應的SELECT 語句。
格式:
CURSOR cursor_name [ (parameter[, parameter ]…)]
[ RETURN datatype ]
IS
select_statement;
遊標參數只能爲輸入參數,其格式爲:
parameter_name [ IN ] datatype [ {:= | DEFAULT} expression ]
在指定數據類型時,不能使用長度約束。如NUMBER(4),CHAR(10) 等都是錯誤的。
[RETURN datatype]是可選的,表示遊標返回數據的數據。若是選擇,則應該嚴格與select_statement中的選擇列表在次序和數據類型上匹配。通常是記錄數據類型或帶「%ROWTYPE」的數據。
l 打開遊標:就是執行遊標所對應的SELECT 語句,將其查詢結果放入工做區,而且指針指向工做區的首部,標識遊標結果集合。若是遊標查詢語句中帶有FOR UPDATE選項,OPEN 語句還將鎖定數據庫表中游標結果集合對應的數據行。
格式:
OPEN cursor_name [ ([parameter => ] value [ , [parameter => ] value]…)];
在向遊標傳遞參數時,可使用與函數參數相同的傳值方法,即位置表示法和名稱表示法。PL/SQL 程序不能用OPEN 語句重複打開一個遊標。
l 提取遊標數據:就是檢索結果集合中的數據行,放入指定的輸出變量中。
格式:
FETCH cursor_name INTO {variable_list | record_variable };
執行FETCH語句時,每次返回一個數據行,而後自動將遊標移動指向下一個數據行。當檢索到最後一行數據時,若是再次執行FETCH語句,將操做失敗,並將遊標屬性%NOTFOUND置爲TRUE。因此每次執行完FETCH語句後,檢查遊標屬性%NOTFOUND就能夠判斷FETCH語句是否執行成功並返回一個數據行,以便肯定是否給對應的變量賦了值。
l 對該記錄進行處理;
l 繼續處理,直到活動集合中沒有記錄;
l 關閉遊標:當提取和處理完遊標結果集合數據後,應及時關閉遊標,以釋放該遊標所佔用的系統資源,並使該遊標的工做區變成無效,不能再使用FETCH 語句取其中數據。關閉後的遊標可使用OPEN 語句從新打開。
格式:
CLOSE cursor_name;
注:定義的遊標不能有INTO 子句。
例1. 查詢前10名員工的信息。
DECLARE
CURSOR c_cursor
IS SELECT first_name || last_name, Salary
FROM EMPLOYEES
WHERE rownum < 11;
v_ename EMPLOYEES.first_name %TYPE;
v_sal EMPLOYEES.Salary %TYPE;
BEGIN
OPEN c_cursor;
FETCH c_cursor INTO v_ename, v_sal;
WHILE c_cursor %FOUND LOOP
DBMS_OUTPUT.PUT_LINE(v_ename || ' --- ' ||to_char(v_sal) );
FETCH c_cursor INTO v_ename, v_sal;
END LOOP;
CLOSE c_cursor;
END;
例2. 遊標參數的傳遞方法。
DECLARE
DeptRec DEPARTMENTS %ROWTYPE;
Dept_name DEPARTMENTS.DEPARTMENT_NAME %TYPE;
Dept_loc DEPARTMENTS.LOCATION_ID %TYPE;
CURSOR c1 IS
SELECT DEPARTMENT_NAME, LOCATION_ID FROM DEPARTMENTS
WHERE DEPARTMENT_ID <= 30;
CURSOR c2(dept_no NUMBER DEFAULT 10) IS
SELECT DEPARTMENT_NAME, LOCATION_ID FROM DEPARTMENTS
WHERE DEPARTMENT_ID <= dept_no;
CURSOR c3(dept_no NUMBER DEFAULT 10) IS
SELECT * FROM DEPARTMENTS
WHERE DEPARTMENTS.DEPARTMENT_ID <=dept_no;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO dept_name, dept_loc;
EXIT WHEN c1 %NOTFOUND;
DBMS_OUTPUT.PUT_LINE(dept_name || ' --- ' ||dept_loc);
END LOOP;
CLOSE c1;
OPEN c2;
LOOP
FETCH c2 INTO dept_name, dept_loc;
EXIT WHEN c2 %NOTFOUND;
DBMS_OUTPUT.PUT_LINE(dept_name || ' --- ' ||dept_loc);
END LOOP;
CLOSE c2;
OPEN c3(dept_no => 20);
LOOP
FETCH c3 INTO deptrec;
EXIT WHEN c3 %NOTFOUND;
DBMS_OUTPUT.PUT_LINE(deptrec.DEPARTMENT_ID || ' --- ' ||deptrec.DEPARTMENT_NAME || ' --- ' ||deptrec.LOCATION_ID);
END LOOP;
CLOSE c3;
END;
2.遊標屬性
Cursor_name%FOUND 布爾型屬性,當最近一次提取遊標操做FETCH成功則爲 TRUE,不然爲FALSE;
Cursor_name%NOTFOUND 布爾型屬性,與%FOUND相反;
Cursor_name%ISOPEN 布爾型屬性,當遊標已打開時返回 TRUE;
Cursor_name%ROWCOUNT 數字型屬性,返回已從遊標中讀取的記錄數。
例3:給工資低於1200 的員工增長工資50。
DECLARE
v_empno EMPLOYEES.EMPLOYEE_ID %TYPE;
v_sal EMPLOYEES.Salary %TYPE;
CURSOR c_cursor IS SELECT EMPLOYEE_ID, Salary FROM EMPLOYEES;
BEGIN
OPEN c_cursor;
LOOP
FETCH c_cursor INTO v_empno, v_sal;
EXIT WHEN c_cursor %NOTFOUND;
IF v_sal <= 1200 THEN
UPDATE EMPLOYEES SET Salary =Salary + 50 WHERE EMPLOYEE_ID =v_empno;
DBMS_OUTPUT.PUT_LINE( ' 編碼爲 ' ||v_empno || ' 工資已更新! ');
END IF;
DBMS_OUTPUT.PUT_LINE( ' 記錄數: ' || c_cursor % ROWCOUNT);
END LOOP;
CLOSE c_cursor;
END;
例4:沒有參數且沒有返回值的遊標。
DECLARE
v_f_name employees.first_name %TYPE;
v_j_id employees.job_id %TYPE;
CURSOR c1 -- 聲明遊標,沒有參數沒有返回值
IS
SELECT first_name, job_id FROM employees
WHERE department_id = 20;
BEGIN
OPEN c1; -- 打開遊標
LOOP
FETCH c1 INTO v_f_name, v_j_id; -- 提取遊標
IF c1 %FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_f_name || ' 的崗位是 ' ||v_j_id);
ELSE
DBMS_OUTPUT.PUT_LINE( ' 已經處理完結果集了 ');
EXIT;
END IF;
END LOOP;
CLOSE c1; -- 關閉遊標
END;
例5:有參數且沒有返回值的遊標。
DECLARE
v_f_name employees.first_name %TYPE;
v_h_date employees.hire_date %TYPE;
CURSOR c2(dept_id NUMBER, j_id VARCHAR2) -- 聲明遊標,有參數沒有返回值
IS
SELECT first_name, hire_date FROM employees
WHERE department_id = dept_id AND job_id = j_id;
BEGIN
OPEN c2( 90, ' AD_VP '); -- 打開遊標,傳遞參數值
LOOP
FETCH c2 INTO v_f_name, v_h_date; -- 提取遊標
IF c2 %FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_f_name || ' 的僱傭日期是 ' ||v_h_date);
ELSE
DBMS_OUTPUT.PUT_LINE( ' 已經處理完結果集了 ');
EXIT;
END IF;
END LOOP;
CLOSE c2; -- 關閉遊標
END;
例6:有參數且有返回值的遊標。
DECLARE
TYPE emp_record_type IS RECORD(
f_name employees.first_name %TYPE,
h_date employees.hire_date %TYPE);
v_emp_record EMP_RECORD_TYPE;
CURSOR c3(dept_id NUMBER, j_id VARCHAR2) -- 聲明遊標,有參數有返回值
RETURN EMP_RECORD_TYPE
IS
SELECT first_name, hire_date FROM employees
WHERE department_id = dept_id AND job_id = j_id;
BEGIN
OPEN c3(j_id => ' AD_VP ', dept_id => 90); -- 打開遊標,傳遞參數值
LOOP
FETCH c3 INTO v_emp_record; -- 提取遊標
IF c3 %FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name || ' 的僱傭日期是 '
||v_emp_record.h_date);
ELSE
DBMS_OUTPUT.PUT_LINE( ' 已經處理完結果集了 ');
EXIT;
END IF;
END LOOP;
CLOSE c3; -- 關閉遊標
END;
例7:基於遊標定義記錄變量。
DECLARE
CURSOR c4(dept_id NUMBER, j_id VARCHAR2) -- 聲明遊標,有參數沒有返回值
IS
SELECT first_name f_name, hire_date FROM employees
WHERE department_id = dept_id AND job_id = j_id;
-- 基於遊標定義記錄變量,比聲明記錄類型變量要方便,不容易出錯
v_emp_record c4 %ROWTYPE;
BEGIN
OPEN c4( 90, ' AD_VP '); -- 打開遊標,傳遞參數值
LOOP
FETCH c4 INTO v_emp_record; -- 提取遊標
IF c4 %FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name || ' 的僱傭日期是 '
||v_emp_record.hire_date);
ELSE
DBMS_OUTPUT.PUT_LINE( ' 已經處理完結果集了 ');
EXIT;
END IF;
END LOOP;
CLOSE c4; -- 關閉遊標
END;
3. 遊標的FOR循環
PL/SQL語言提供了遊標FOR循環語句,自動執行遊標的OPEN、FETCH、CLOSE語句和循環語句的功能;當進入循環時,遊標FOR循環語句自動打開遊標,並提取第一行遊標數據,當程序處理完當前所提取的數據而進入下一次循環時,遊標FOR循環語句自動提取下一行數據供程序處理,當提取完結果集合中的全部數據行後結束循環,並自動關閉遊標。
格式:
FOR index_variable IN cursor_name [ (value[, value ]…)] LOOP
-- 遊標數據處理代碼
END LOOP;
其中:
index_variable爲遊標FOR 循環語句隱含聲明的索引變量,該變量爲記錄變量,其結構與遊標查詢語句返回的結構集合的結構相同。在程序中能夠經過引用該索引記錄變量元素來讀取所提取的遊標數據,index_variable中各元素的名稱與遊標查詢語句選擇列表中所制定的列名相同。若是在遊標查詢語句的選擇列表中存在計算列,則必須爲這些計算列指定別名後才能經過遊標FOR 循環語句中的索引變量來訪問這些列數據。
注:不要在程序中對遊標進行人工操做;不要在程序中定義用於控制FOR循環的記錄。
例8:
DECLARE
CURSOR c_sal IS SELECT employee_id, first_name || last_name ename, salary
FROM employees ;
BEGIN
-- 隱含打開遊標
FOR v_sal IN c_sal LOOP
-- 隱含執行一個FETCH語句
DBMS_OUTPUT.PUT_LINE(to_char(v_sal.employee_id) || ' --- ' || v_sal.ename || ' --- ' ||to_char(v_sal.salary)) ;
-- 隱含監測c_sal%NOTFOUND
END LOOP;
-- 隱含關閉遊標
END;
例9:當所聲明的遊標帶有參數時,經過遊標FOR 循環語句爲遊標傳遞參數。
DECLARE
CURSOR c_cursor(dept_no NUMBER DEFAULT 10)
IS
SELECT department_name, location_id FROM departments WHERE department_id <= dept_no;
BEGIN
DBMS_OUTPUT.PUT_LINE( ' 當dept_no參數值爲30: ');
FOR c1_rec IN c_cursor( 30) LOOP DBMS_OUTPUT.PUT_LINE(c1_rec.department_name || ' --- ' ||c1_rec.location_id);
END LOOP;
DBMS_OUTPUT.PUT_LINE(CHR( 10) || ' 使用默認的dept_no參數值10: ');
FOR c1_rec IN c_cursor LOOP DBMS_OUTPUT.PUT_LINE(c1_rec.department_name || ' --- ' ||c1_rec.location_id);
END LOOP;
END;
例10:PL/SQL還容許在遊標FOR循環語句中使用子查詢來實現遊標的功能。
BEGIN
FOR c1_rec IN( SELECT department_name, location_id FROM departments) LOOP DBMS_OUTPUT.PUT_LINE(c1_rec.department_name || ' --- ' ||c1_rec.location_id);
END LOOP;
END;
4.1.2 處理隱式遊標
顯式遊標主要是用於對查詢語句的處理,尤爲是在查詢結果爲多條記錄的狀況下;而對於非查詢語句,如修改、刪除操做,則由ORACLE 系統自動地爲這些操做設置遊標並建立其工做區,這些由系統隱含建立的遊標稱爲隱式遊標,隱式遊標的名字爲SQL,這是由ORACLE 系統定義的。對於隱式遊標的操做,如定義、打開、取值及關閉操做,都由ORACLE 系統自動地完成,無需用戶進行處理。用戶只能經過隱式遊標的相關屬性,來完成相應的操做。在隱式遊標的工做區中,所存放的數據是與用戶自定義的顯示遊標無關的、最新處理的一條SQL 語句所包含的數據。
格式調用爲: SQL%
注:INSERT, UPDATE, DELETE, SELECT 語句中沒必要明肯定義遊標。
隱式遊標屬性
屬性 |
值 |
SELECT |
INSERT |
UPDATE |
DELETE |
SQL%ISOPEN |
|
FALSE |
FALSE |
FALSE |
FALSE |
SQL%FOUND |
TRUE |
有結果 |
|
成功 |
成功 |
SQL%FOUND |
FALSE |
沒結果 |
|
失敗 |
失敗 |
SQL%NOTFUOND |
TRUE |
沒結果 |
|
失敗 |
失敗 |
SQL%NOTFOUND |
FALSE |
有結果 |
|
成功 |
失敗 |
SQL%ROWCOUNT |
|
返回行數,只爲1 |
插入的行數 |
修改的行數 |
刪除的行數 |
例11: 刪除EMPLOYEES表中某部門的全部員工,若是該部門中已沒有員工,則在DEPARTMENT表中刪除該部門。
DECLARE
V_deptno department_id %TYPE : =&p_deptno;
BEGIN
DELETE FROM employees WHERE department_id =v_deptno;
IF SQL %NOTFOUND THEN
DELETE FROM departments WHERE department_id =v_deptno;
END IF;
END;
例12: 經過隱式遊標SQL的%ROWCOUNT屬性來了解修改了多少行。
DECLARE
v_rows NUMBER;
BEGIN
-- 更新數據
UPDATE employees SET salary = 30000
WHERE department_id = 90 AND job_id = ' AD_VP ';
-- 獲取默認遊標的屬性值
v_rows : = SQL % ROWCOUNT;
DBMS_OUTPUT.PUT_LINE( ' 更新了 ' ||v_rows || ' 個僱員的工資 ');
-- 回退更新,以便使數據庫的數據保持原樣
ROLLBACK;
END;
4.1.3 關於 NO_DATA_FOUND 和 %NOTFOUND的區別
SELECT … INTO 語句觸發 NO_DATA_FOUND;
當一個顯式遊標的WHERE子句未找到時觸發%NOTFOUND;
當UPDATE或DELETE 語句的WHERE 子句未找到時觸發 SQL%NOTFOUND;在提取循環中要用 %NOTFOUND 或%FOUND 來肯定循環的退出條件,不要用 NO_DATA_FOUND.4.1.4 使用遊標更新和刪除數據
遊標修改和刪除操做是指在遊標定位下,修改或刪除表中指定的數據行。這時,要求遊標查詢語句中必須使用FOR UPDATE選項,以便在打開遊標時鎖定遊標結果集合在表中對應數據行的全部列和部分列。
爲了對正在處理(查詢)的行不被另外的用戶改動,ORACLE 提供一個 FOR UPDATE 子句來對所選擇的行進行鎖住。該需求迫使ORACLE鎖定遊標結果集合的行,能夠防止其餘事務處理更新或刪除相同的行,直到您的事務處理提交或回退爲止。
語法:
SELECT column_list FROM table_list FOR UPDATE [ OF column[, column ]…] [ NOWAIT ]
若是另外一個會話已對活動集中的行加了鎖,那麼SELECT FOR UPDATE操做一直等待到其它的會話釋放這些鎖後才繼續本身的操做,對於這種狀況,當加上NOWAIT子句時,若是這些行真的被另外一個會話鎖定,則OPEN當即返回並給出:
ORA-0054 :resource busy and acquire with nowait specified.
若是使用 FOR UPDATE 聲明遊標,則可在DELETE和UPDATE 語句中使用
WHERE CURRENT OF cursor_name子句,修改或刪除遊標結果集合當前行對應的數據庫表中的數據行。
例13:從EMPLOYEES表中查詢某部門的員工狀況,將其工資最低定爲 1500;
DECLARE
V_deptno employees.department_id %TYPE : =&p_deptno;
CURSOR emp_cursor
IS
SELECT employees.employee_id, employees.salary
FROM employees WHERE employees.department_id =v_deptno
FOR UPDATE NOWAIT;
BEGIN
FOR emp_record IN emp_cursor LOOP
IF emp_record.salary < 1500 THEN
UPDATE employees SET salary = 1500
WHERE CURRENT OF emp_cursor;
END IF;
END LOOP;
-- COMMIT;
END;
例14:將EMPLOYEES表中部門編碼爲90、崗位爲AD_VP的僱員的工資都更新爲2000元;
DECLARE
v_emp_record employees %ROWTYPE;
CURSOR c1
IS
SELECT * FROM employees FOR UPDATE;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_emp_record;
EXIT WHEN c1 %NOTFOUND;
IF v_emp_record.department_id = 90 AND
v_emp_record.job_id = ' AD_VP '
THEN
UPDATE employees SET salary = 20000
WHERE CURRENT OF c1; -- 更新當前遊標行對應的數據行
END IF;
END LOOP;
COMMIT; -- 提交已經修改的數據
CLOSE c1;
END;
4.2 遊標變量
與 遊標同樣,遊標變量也是一個指向多行查詢結果集合中當前數據行的指針。但與遊標不一樣的是,遊標變量是動態的,而遊標是靜態的。遊標只能與指定的查詢相連, 即固定指向一個查詢的內存處理區域,而遊標變量則可與不一樣的查詢語句相連,它能夠指向不一樣查詢語句的內存處理區域(但不能同時指向多個內存處理區域,在某 一時刻只能與一個查詢語句相連),只要這些查詢語句的返回類型兼容便可。
4.2.1 聲明遊標變量
遊標變量爲一個指針,它屬於參照類型,因此在聲明遊標變量類型以前必須先定義遊標變量類型。在PL/SQL中,能夠在塊、子程序和包的聲明區域內定義遊標變量類型。
語法格式爲:
TYPE ref_type_name IS REF CURSOR
[ RETURN return_type ];
其中:ref_type_name爲新定義的遊標變量類型名稱;
return_type 爲遊標變量的返回值類型,它必須爲記錄變量。
在定義遊標變量類型時,能夠採用強類型定義和弱類型定義兩種。強類型定義必須指定遊標變量的返回值類型,而弱類型定義則不說明返回值類型。
聲明一個遊標變量的兩個步驟:
步驟一:定義一個REF CURSOU數據類型,如:
TYPE ref_cursor_type IS REF CURSOR;
步驟二:聲明一個該數據類型的遊標變量,如:
cv_ref REF_CURSOR_TYPE;
例:建立兩個強類型定義遊標變量和一個弱類型遊標變量:
DECLARE
TYPE deptrecord IS RECORD(
Deptno departments.department_id %TYPE,
Dname departments.department_name %TYPE,
Loc departments.location_id %TYPE
);
TYPE deptcurtype IS REF CURSOR RETURN departments %ROWTYPE;
TYPE deptcurtyp1 IS REF CURSOR RETURN deptrecord;
TYPE curtype IS REF CURSOR;
Dept_c1 deptcurtype;
Dept_c2 deptcurtyp1;
Cv curtype;
4.2.2 遊標變量操做
與遊標同樣,遊標變量操做也包括打開、提取和關閉三個步驟。
1. 打開遊標變量
打開遊標變量時使用的是OPEN…FOR 語句。格式爲:
OPEN {cursor_variable_name | :host_cursor_variable_name}
FOR select_statement;
其中:cursor_variable_name爲遊標變量,host_cursor_variable_name爲PL/SQL主機環境(如OCI: ORACLE Call Interface,Pro*c 程序等)中聲明的遊標變量。
OPEN…FOR 語句能夠在關閉當前的遊標變量以前從新打開遊標變量,而不會致使CURSOR_ALREAD_OPEN異常錯誤。新打開遊標變量時,前一個查詢的內存處理區將被釋放。
2. 提取遊標變量數據
使用FETCH語句提取遊標變量結果集合中的數據。格式爲:
FETCH {cursor_variable_name | :host_cursor_variable_name}
INTO {variable [ , variable ]… | record_variable};
其中:cursor_variable_name和host_cursor_variable_name分別爲遊標變量和宿主遊標變量名稱;variable和record_variable分別爲普通變量和記錄變量名稱。
3. 關閉遊標變量
CLOSE語句關閉遊標變量,格式爲:
CLOSE {cursor_variable_name | :host_cursor_variable_name}
其中:cursor_variable_name和host_cursor_variable_name分別爲遊標變量和宿主遊標變量名稱,若是應用程序試圖關閉一個未打開的遊標變量,則將致使INVALID_CURSOR異常錯誤。
例15:強類型參照遊標變量類型
DECLARE
TYPE emp_job_rec IS RECORD(
Employee_id employees.employee_id %TYPE,
Employee_name employees.first_name %TYPE,
Job_title employees.job_id %TYPE
);
TYPE emp_job_refcur_type IS REF CURSOR RETURN emp_job_rec;
Emp_refcur emp_job_refcur_type ;
Emp_job emp_job_rec;
BEGIN
OPEN emp_refcur FOR
SELECT employees.employee_id, employees.first_name ||employees.last_name, employees.job_id
FROM employees
ORDER BY employees.department_id;
FETCH emp_refcur INTO emp_job;
WHILE emp_refcur %FOUND LOOP
DBMS_OUTPUT.PUT_LINE(emp_job.employee_id || ' : ' ||emp_job.employee_name || ' is a ' ||emp_job.job_title);
FETCH emp_refcur INTO emp_job;
END LOOP;
END;
例16:弱類型參照遊標變量類型
PROMPT
PROMPT ' What table would you like to see? '
ACCEPT tab PROMPT ' (D)epartment, or (E)mployees: '
DECLARE
Type refcur_t IS REF CURSOR;
Refcur refcur_t;
TYPE sample_rec_type IS RECORD (
Id number,
Description VARCHAR2 ( 30)
);
sample sample_rec_type;
selection varchar2( 1) : = UPPER (SUBSTR ( ' &tab ', 1, 1));
BEGIN
IF selection = ' D ' THEN
OPEN refcur FOR
SELECT departments.department_id, departments.department_name FROM departments;
DBMS_OUTPUT.PUT_LINE( ' Department data ');
ELSIF selection = ' E ' THEN
OPEN refcur FOR
SELECT employees.employee_id, employees.first_name || ' is a ' ||employees.job_id FROM employees;
DBMS_OUTPUT.PUT_LINE( ' Employee data ');
ELSE
DBMS_OUTPUT.PUT_LINE( ' Please enter '' D '' or '' E ''');
RETURN;
END IF;
DBMS_OUTPUT.PUT_LINE( ' ---------------------- ');
FETCH refcur INTO sample;
WHILE refcur %FOUND LOOP
DBMS_OUTPUT.PUT_LINE(sample.id || ' : ' ||sample.description);
FETCH refcur INTO sample;
END LOOP;
CLOSE refcur;
END;
例17:使用遊標變量(沒有RETURN子句)
DECLARE
-- 定義一個遊標數據類型
TYPE emp_cursor_type IS REF CURSOR;
-- 聲明一個遊標變量
c1 EMP_CURSOR_TYPE;
-- 聲明兩個記錄變量
v_emp_record employees %ROWTYPE;
v_reg_record regions %ROWTYPE;
BEGIN
OPEN c1 FOR SELECT * FROM employees WHERE department_id = 20;
LOOP
FETCH c1 INTO v_emp_record;
EXIT WHEN c1 %NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp_record.first_name || ' 的僱傭日期是 '
||v_emp_record.hire_date);
END LOOP;
-- 將同一個遊標變量對應到另外一個SELECT語句
OPEN c1 FOR SELECT * FROM regions WHERE region_id IN( 1, 2);
LOOP
FETCH c1 INTO v_reg_record;
EXIT WHEN c1 %NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_reg_record.region_id || ' 表示 '
||v_reg_record.region_name);
END LOOP;
CLOSE c1;
END;
例18:使用遊標變量(有RETURN子句)
DECLARE
-- 定義一個與employees表中的這幾個列相同的記錄數據類型
TYPE emp_record_type IS RECORD(
f_name employees.first_name %TYPE,
h_date employees.hire_date %TYPE,
j_id employees.job_id %TYPE);
-- 聲明一個該記錄數據類型的記錄變量
v_emp_record EMP_RECORD_TYPE;
-- 定義一個遊標數據類型
TYPE emp_cursor_type IS REF CURSOR
RETURN EMP_RECORD_TYPE;
-- 聲明一個遊標變量
c1 EMP_CURSOR_TYPE;
BEGIN
OPEN c1 FOR SELECT first_name, hire_date, job_id
FROM employees WHERE department_id = 20;
LOOP
FETCH c1 INTO v_emp_record;
EXIT WHEN c1 %NOTFOUND;
DBMS_OUTPUT.PUT_LINE( ' 僱員名稱: ' ||v_emp_record.f_name
|| ' 僱傭日期: ' ||v_emp_record.h_date
|| ' 崗位: ' ||v_emp_record.j_id);
END LOOP;
CLOSE c1;
END;
即便是寫得最好的PL/SQL程序也會遇到錯誤或未預料到的事件。一個優秀的程序都應該可以正確處理各類出錯狀況,並儘量從錯誤中恢復。任何ORACLE錯誤(報告爲ORA-xxxxx形式的Oracle錯誤號)、PL/SQL運行錯誤或用戶定義條件(不一寫是錯誤),均可以。固然了,PL/SQL編譯錯誤不能經過PL/SQL異常處理來處理,由於這些錯誤發生在PL/SQL程序執行以前。
ORACLE 提供異常狀況(EXCEPTION)和異常處理(EXCEPTION HANDLER)來實現錯誤處理。
5.1 異常處理概念
異常狀況處理(EXCEPTION)是用來處理正常執行過程當中未預料的事件,程序塊的異常處理預約義的錯誤和自定義錯誤,因爲PL/SQL程序塊一旦產生異常而沒有指出如何處理時,程序就會自動終止整個程序運行.
有三種類型的異常錯誤:
1. 預約義 ( Predefined )錯誤
ORACLE預約義的異常狀況大約有24個。對這種異常狀況的處理,無需在程序中定義,由ORACLE自動將其引起。
2. 非預約義 ( Predefined )錯誤
即其餘標準的ORACLE錯誤。對這種異常狀況的處理,須要用戶在程序中定義,而後由ORACLE自動將其引起。
3. 用戶定義(User_define) 錯誤
程序執行過程當中,出現編程人員認爲的非正常狀況。對這種異常狀況的處理,須要用戶在程序中定義,而後顯式地在程序中將其引起。
異常處理部分通常放在 PL/SQL 程序體的後半部,結構爲:
EXCEPTION
WHEN first_exception THEN <code to handle first exception >
WHEN second_exception THEN <code to handle second exception >
WHEN OTHERS THEN <code to handle others exception >
END;
異常處理能夠按任意次序排列,但 OTHERS 必須放在最後.
5.1.1 預約義的異常處理
預約義說明的部分 ORACLE 異常錯誤
錯誤號 |
異常錯誤信息名稱 |
說明 |
ORA-0001 |
Dup_val_on_index |
違反了惟一性限制 |
ORA-0051 |
Timeout-on-resource |
在等待資源時發生超時 |
ORA-0061 |
Transaction-backed-out |
因爲發生死鎖事務被撤消 |
ORA-1001 |
Invalid-CURSOR |
試圖使用一個無效的遊標 |
ORA-1012 |
Not-logged-on |
沒有鏈接到ORACLE |
ORA-1017 |
Login-denied |
無效的用戶名/口令 |
ORA-1403 |
No_data_found |
SELECT INTO沒有找到數據 |
ORA-1422 |
Too_many_rows |
SELECT INTO 返回多行 |
ORA-1476 |
Zero-divide |
試圖被零除 |
ORA-1722 |
Invalid-NUMBER |
轉換一個數字失敗 |
ORA-6500 |
Storage-error |
內存不夠引起的內部錯誤 |
ORA-6501 |
Program-error |
內部錯誤 |
ORA-6502 |
Value-error |
轉換或截斷錯誤 |
ORA-6504 |
Rowtype-mismatch |
宿主遊標變量與 PL/SQL變量有不兼容行類型 |
ORA-6511 |
CURSOR-already-OPEN |
試圖打開一個已處於打開狀態的遊標 |
ORA-6530 |
Access-INTO-null |
試圖爲null 對象的屬性賦值 |
ORA-6531 |
Collection-is-null |
試圖將Exists 之外的集合( collection)方法應用於一個null pl/sql 表上或varray上 |
ORA-6532 |
Subscript-outside-limit |
對嵌套或varray索引得引用超出聲明範圍之外 |
ORA-6533 |
Subscript-beyond-count |
對嵌套或varray 索引得引用大於集合中元素的個數. |
對這種異常狀況的處理,只需在PL/SQL塊的異常處理部分,直接引用相應的異常狀況名,並對其完成相應的異常錯誤處理便可。
例1:更新指定員工工資,如工資小於1500,則加100;
DECLARE
v_empno employees.employee_id %TYPE : = &empno;
v_sal employees.salary %TYPE;
BEGIN
SELECT salary INTO v_sal FROM employees WHERE employee_id = v_empno;
IF v_sal <= 1500 THEN
UPDATE employees SET salary = salary + 100 WHERE employee_id =v_empno;
DBMS_OUTPUT.PUT_LINE( ' 編碼爲 ' ||v_empno || ' 員工工資已更新! ');
ELSE
DBMS_OUTPUT.PUT_LINE( ' 編碼爲 ' ||v_empno || ' 員工工資已經超過規定值! ');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE( ' 數據庫中沒有編碼爲 ' ||v_empno || ' 的員工 ');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE( ' 程序運行錯誤!請使用遊標 ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END;
5.1.2 非預約義的異常處理
對於這類異常狀況的處理,首先必須對非定義的ORACLE錯誤進行定義。步驟以下:
1. 在PL/SQL 塊的定義部分定義異常狀況:
<異常狀況 > EXCEPTION;
2. 將其定義好的異常狀況,與標準的ORACLE錯誤聯繫起來,使用EXCEPTION_INIT語句:
PRAGMA EXCEPTION_INIT( <異常狀況 >, <錯誤代碼 >);
3. 在PL/SQL 塊的異常狀況處理部分對異常狀況作出相應的處理。
例2:刪除指定部門的記錄信息,以確保該部門沒有員工。
INSERT INTO departments VALUES( 50, ' FINANCE ', ' CHICAGO ');
DECLARE
v_deptno departments.department_id %TYPE : = &deptno;
deptno_remaining EXCEPTION;
PRAGMA EXCEPTION_INIT(deptno_remaining, - 2292);
/* -2292 是違反一致性約束的錯誤代碼 */
BEGIN
DELETE FROM departments WHERE department_id = v_deptno;
EXCEPTION
WHEN deptno_remaining THEN
DBMS_OUTPUT.PUT_LINE( ' 違反數據完整性約束! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END;
5.1.3 用戶自定義的異常處理
當與一個異常錯誤相關的錯誤出現時,就會隱含觸發該異常錯誤。用戶定義的異常錯誤是經過顯式使用 RAISE 語句來觸發。當引起一個異常錯誤時,控制就轉向到 EXCEPTION塊異常錯誤部分,執行錯誤處理代碼。
對於這類異常狀況的處理,步驟以下:
1. 在PL/SQL 塊的定義部分定義異常狀況:
<異常狀況 > EXCEPTION;
2. RAISE <異常狀況>;
3. 在PL/SQL 塊的異常狀況處理部分對異常狀況作出相應的處理。
例3:更新指定員工工資,增長100;
DECLARE
v_empno employees.employee_id %TYPE : =&empno;
no_result EXCEPTION;
BEGIN
UPDATE employees SET salary = salary + 100 WHERE employee_id = v_empno;
IF SQL %NOTFOUND THEN
RAISE no_result;
END IF;
EXCEPTION
WHEN no_result THEN
DBMS_OUTPUT.PUT_LINE( ' 你的數據更新語句失敗了! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END;
5.1.4 用戶定義的異常處理
調用DBMS_STANDARD(ORACLE提供的包)包所定義的RAISE_APPLICATION_ERROR過程,能夠從新定義異常錯誤消息,它爲應用程序提供了一種與ORACLE交互的方法。
RAISE_APPLICATION_ERROR 的語法以下:
RAISE_APPLICATION_ERROR(error_number,error_message, [ keep_errors ] );
這裏的error_number 是從 –20,000 到 –20,999 之間的參數,
error_message 是相應的提示信息(< 2048 字節),
keep_errors 爲可選,若是keep_errors =TRUE ,則新錯誤將被添加到已經引起的錯誤列表中。若是keep_errors=FALSE(缺省),則新錯誤將替換當前的錯誤列表。
例4:建立一個函數get_salary, 該函數檢索指定部門的工資總和,其中定義了-20991和-20992號錯誤,分別處理參數爲空和非法部門代碼兩種錯誤:
CREATE TABLE errlog(
Errcode NUMBER,
Errtext CHAR( 40));
CREATE OR REPLACE FUNCTION get_salary(p_deptno NUMBER)
RETURN NUMBER
AS
v_sal NUMBER;
BEGIN
IF p_deptno IS NULL THEN
RAISE_APPLICATION_ERROR( - 20991, ’部門代碼爲空’);
ELSIF p_deptno < 0 THEN
RAISE_APPLICATION_ERROR( - 20992, ’無效的部門代碼’);
ELSE
SELECT SUM(employees.salary) INTO v_sal FROM employees
WHERE employees.department_id =p_deptno;
RETURN v_sal;
END IF;
END;
DECLARE
V_salary NUMBER( 7, 2);
V_sqlcode NUMBER;
V_sqlerr VARCHAR2( 512);
Null_deptno EXCEPTION;
Invalid_deptno EXCEPTION;
PRAGMA EXCEPTION_INIT(null_deptno, - 20991);
PRAGMA EXCEPTION_INIT(invalid_deptno, - 20992);
BEGIN
V_salary : =get_salary( 10);
DBMS_OUTPUT.PUT_LINE( ' 10號部門工資: ' || TO_CHAR(V_salary));
BEGIN
V_salary : =get_salary( - 10);
EXCEPTION
WHEN invalid_deptno THEN
V_sqlcode : =SQLCODE;
V_sqlerr : =SQLERRM;
INSERT INTO errlog(errcode, errtext)
VALUES(v_sqlcode, v_sqlerr);
COMMIT;
END inner1;
V_salary : =get_salary( 20);
DBMS_OUTPUT.PUT_LINE( ' 部門號爲20的工資爲: ' ||TO_CHAR(V_salary));
BEGIN
V_salary : =get_salary( NULL);
END inner2;
V_salary : = get_salary( 30);
DBMS_OUTPUT.PUT_LINE( ' 部門號爲30的工資爲: ' ||TO_CHAR(V_salary));
EXCEPTION
WHEN null_deptno THEN
V_sqlcode : =SQLCODE;
V_sqlerr : =SQLERRM;
INSERT INTO errlog(errcode, errtext) VALUES(v_sqlcode, v_sqlerr);
COMMIT;
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END outer;
例5:定義觸發器,使用RAISE_APPLICATION_ERROR阻止沒有員工姓名的新員式記錄插入:
CREATE OR REPLACE TRIGGER tr_insert_emp
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
IF :new.first_name IS NULL OR :new.last_name is null THEN
RAISE_APPLICATION_ERROR( - 20000, ' Employee must have a name. ');
END IF;
END;
5.2 異常錯誤傳播
因爲異常錯誤能夠在聲明部分和執行部分以及異常錯誤部分出現,於是在不一樣部分引起的異常錯誤也不同。
5.2.1 在執行部分引起異常錯誤
當一個異常錯誤在執行部分引起時,有下列狀況:
l 若是當前塊對該異常錯誤設置了處理,則執行它併成功完成該塊的執行,而後控制轉給包含塊。
l 若是沒有對當前塊異常錯誤設置定義處理器,則經過在包含塊中引起它來傳播異常錯誤。而後對該包含塊執行步驟1)。
5.2.2 在聲明部分引起異常錯誤
若是在聲明部分引發異常狀況,即在聲明部分出現錯誤,那麼該錯誤就能影響到其它的塊。好比在有以下的PL/SQL程序:
DECLARE
name varchar2( 12): = ' EricHu ';
其它語句
BEGIN
其它語句
EXCEPTION
WHEN OTHERS THEN
其它語句
END;
例子中,因爲Abc number(3)=’abc’; 出錯,儘管在EXCEPTION中說明了WHEN OTHERS THEN語句,但WHEN OTHERS THEN也不會被執行。 可是若是在該錯誤語句塊的外部有一個異常錯誤,則該錯誤能被抓住,如:
BEGIN
DECLARE
name varchar2( 12): = ' EricHu ';
其它語句
BEGIN
其它語句
EXCEPTION
WHEN OTHERS THEN
其它語句
END;
EXCEPTION
WHEN OTHERS THEN
其它語句
END;
5.3 異常錯誤處理編程
在通常的應用處理中,建議程序人員要用異常處理,由於若是程序中不聲明任何異常處理,則在程序運行出錯時,程序就被終止,而且也不提示任何信息。下面是使用系統提供的異常來編程的例子。
5.4 在 PL/SQL 中使用 SQLCODE, SQLERRM異常處理函數
因爲ORACLE 的錯信息最大長度是512字節,爲了獲得完整的錯誤提示信息,咱們可用 SQLERRM和 SUBSTR 函數一塊兒獲得錯誤提示信息,方便進行錯誤,特別是若是WHEN OTHERS異常處理器時更爲方便。
SQLCODE 返回遇到的Oracle錯誤號,
SQLERRM 返回遇到的Oracle錯誤信息.
如: SQLCODE=-100 è SQLERRM=’no_data_found ‘
SQLCODE=0 è SQLERRM=’normal, successfual completion’
例6. 將ORACLE錯誤代碼及其信息存入錯誤代碼表
CREATE TABLE errors (errnum NUMBER( 4), errmsg VARCHAR2( 100));
DECLARE
err_msg VARCHAR2( 100);
BEGIN
/* 獲得全部 ORACLE 錯誤信息 */
FOR err_num IN - 100 .. 0 LOOP
err_msg : = SQLERRM(err_num);
INSERT INTO errors VALUES(err_num, err_msg);
END LOOP;
END;
DROP TABLE errors;
例7. 查詢ORACLE錯誤代碼;
BEGIN
INSERT INTO employees(employee_id, first_name,last_name,hire_date,department_id)
VALUES( 2222, ' Eric ', ' Hu ', SYSDATE, 20);
DBMS_OUTPUT.PUT_LINE( ' 插入數據記錄成功! ');
INSERT INTO employees(employee_id, first_name,last_name,hire_date,department_id)
VALUES( 2222, ' 胡 ', ' 勇 ', SYSDATE, 20);
DBMS_OUTPUT.PUT_LINE( ' 插入數據記錄成功! ');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END;
例8. 利用ORACLE錯誤代碼,編寫異常錯誤處理代碼;
DECLARE
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( 3333, ' Eric ', ' Hu ', SYSDATE, 20);
DBMS_OUTPUT.PUT_LINE( ' 插入數據記錄成功! ');
INSERT INTO employees(employee_id, first_name,last_name,hire_date,department_id)
VALUES( 3333, ' 胡 ', ' 勇 ',SYSDATE, 20);
DBMS_OUTPUT.PUT_LINE( ' 插入數據記錄成功! ');
EXCEPTION
WHEN empno_remaining THEN
DBMS_OUTPUT.PUT_LINE( ' 違反數據完整性約束! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END;
6.1 引言
過程與函數(另外還有包與觸發器)是命名的PL/SQL塊(也是用戶的方案對象),被編譯後存儲在數據庫中,以備執行。所以,其它PL/SQL塊能夠按名稱來使用他們。因此,能夠將商業邏輯、企業規則寫成函數或過程保存到數據庫中,以便共享。
過程和函數統稱爲PL/SQL子程序,他們是被命名的PL/SQL塊,均存儲在數據庫中,並經過輸入、輸出參數或輸入/輸出參數與其調用者交換信息。過程和函數的惟一區別是函數總向調用者返回數據,而過程則不返回數據。在本節中,主要介紹:
1. 建立存儲過程和函數。
2. 正確使用系統級的異常處理和用戶定義的異常處理。
3. 創建和管理存儲過程和函數。
6.2 建立函數
1. 建立函數
語法以下:
CREATE [ OR REPLACE ] FUNCTION 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. 獲取某部門的工資總和:
-- 獲取某部門的工資總和
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. 函數的調用
函數聲明時所定義的參數稱爲形式參數,應用程序調用時爲函數傳遞的參數稱爲實際參數。應用程序在調用函數時,可使用如下三種方法向函數傳遞參數:
第一種參數傳遞格式:位置表示法。
即在調用時按形參的排列順序,依次寫出實參的名稱,而將形參與實參關聯起來進行傳遞。用這種方法進行調用,形參與實參的名稱是相互獨立,沒有關係,強調次序纔是重要的。
格式爲:
argument_value1[,argument_value2 …]
例2:計算某部門的工資總和:
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;
第二種參數傳遞格式:名稱表示法。
即在調用時按形參的名稱與實參的名稱,寫出實參對應的形參,而將形參與實參關聯起來進行傳遞。這種方法,形參與實參的名稱是相互獨立的,沒有關係,名稱的對應關係纔是最重要的,次序並不重要。
格式爲:
argument => parameter [,…]
其中:argument 爲形式參數,它必須與函數定義時所聲明的形式參數名稱相同parameter 爲實際參數。
在這種格式中,形勢參數與實際參數成對出現,相互間關係惟一肯定,因此參數的順序能夠任意排列。
例3:計算某部門的工資總和:
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:
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:
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;
具備默認值的函數建立後,在函數調用時,若是沒有爲具備默認值的參數提供實際參數值,函數將使用該參數的默認值。但當調用者爲默認參數提供實際參數時,函數將使用實際參數值。在建立函數時,只能爲輸入參數設置默認值,而不能爲輸入/輸出參數設置默認值。
DECLARE
var VARCHAR(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上創建存儲過程,能夠被多個應用程序調用,能夠向存儲過程傳遞參數,也能夠向存儲過程傳回參數.
建立過程語法:
CREATE [ OR REPLACE ] PROCEDURE 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.用戶鏈接登記記錄;
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.刪除指定員工記錄;
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.插入員工記錄:
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表中插入數據。
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:查詢指定員工記錄;
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.計算指定部門的工資總和,並統計其中的職工數量。
CREATE OR REPLACE
PROCEDURE proc_demo
(
dept_no NUMBER DEFAULT 10,
sal_sum OUT NUMBER,
emp_count OUT NUMBER
)
IS
BEGIN
SELECT SUM(salary), COUNT( *) INTO sal_sum, emp_count
FROM employees WHERE department_id = dept_no;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:你須要的數據不存在! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END proc_demo;
DECLARE
V_num NUMBER;
V_sum NUMBER( 8, 2);
BEGIN
Proc_demo( 30, v_sum, v_num);
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:30號部門工資總和: ' ||v_sum || ' ,人數: ' ||v_num);
Proc_demo(sal_sum => v_sum, emp_count => v_num);
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:10號部門工資總和: ' ||v_sum || ' ,人數: ' ||v_num);
END;
在PL/SQL 程序中還能夠在塊內創建本地函數和過程,這些函數和過程不存儲在數據庫中,但能夠在建立它們的PL/SQL 程序中被重複調用。本地函數和過程在PL/SQL 塊的聲明部分定義,它們的語法格式與存儲函數和過程相同,但不能使用CREATE OR REPLACE 關鍵字。
例13:創建本地過程,用於計算指定部門的工資總和,並統計其中的職工數量;
DECLARE
V_num NUMBER;
V_sum NUMBER( 8, 2);
PROCEDURE proc_demo
(
Dept_no NUMBER DEFAULT 10,
Sal_sum OUT NUMBER,
Emp_count OUT NUMBER
)
IS
BEGIN
SELECT SUM(salary), COUNT( *) INTO sal_sum, emp_count
FROM employees WHERE department_id =dept_no;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE( ' 你須要的數據不存在! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END proc_demo;
-- 調用方法:
BEGIN
Proc_demo( 30, v_sum, v_num);
DBMS_OUTPUT.PUT_LINE( ' 30號部門工資總和: ' ||v_sum || ' ,人數: ' ||v_num);
Proc_demo(sal_sum => v_sum, emp_count => v_num);
DBMS_OUTPUT.PUT_LINE( ' 10號部門工資總和: ' ||v_sum || ' ,人數: ' ||v_num);
END;
6.3.3 AUTHID
過程當中的AUTHID 指令能夠告訴ORACLE ,這個過程使用誰的權限運行.默任狀況下,存儲過程會做爲調用者的過程運行,可是具備設計者的特權.這稱爲設計者權利運行.
例14:創建過程,使用AUTOID DEFINER;
Connect HR /qaz
DROP TABLE logtable;
CREATE table logtable (userid VARCHAR2( 10), logdate date);
CREATE OR REPLACE PROCEDURE logexecution
AUTHID DEFINER
IS
BEGIN
INSERT INTO logtable (userid, logdate) VALUES ( USER, SYSDATE);
END;
GRANT EXECUTE ON logexecution TO PUBLIC;
CONNECT / AS SYSDBA
GRANT CONNECT TO testuser1 IDENTIFIED BY userpwd1;
CONNECT testuser1 /userpwd1
INSERT INTO HR.LOGTABLE VALUES ( USER, SYSDATE);
EXECUTE HR.logexecution
CONNECT HR /qaz
SELECT * FROM HR.logtable;
例15:創建過程,使用AUTOID CURRENT_USER;
CONNECT HR /qaz
CREATE OR REPLACE PROCEDURE logexecution
AUTHID CURRENT_USER
IS
BEGIN
INSERT INTO logtable (userid, logdate) VALUES ( USER, SYSDATE);
END;
GRANT EXECUTE ON logexecution TO PUBLIC;
CONNECT testuser1 /userpwd1
INSERT INTO HR.LOGTABLE VALUES ( USER, SYSDATE);
EXECUTE HR.logexecution
6.3.4 PRAGMA AUTONOMOUS_TRANSACTION
ORACLE8i 能夠支持事務處理中的事務處理的概念.這種子事務處理能夠完成它本身的工做,獨立於父事務處理進行提交或者回滾.經過使用這種方法,開發者就可以這樣的過程,不管父事務處理是提交仍是回滾,它均可以成功執行.
例16:創建過程,使用自動事務處理進行日誌記錄;
DROP TABLE logtable;
CREATE TABLE logtable(
Username varchar2( 20),
Dassate_time date,
Mege varchar2( 60)
);
CREATE TABLE temp_table( N number );
CREATE OR REPLACE PROCEDURE log_message(p_message varchar2)
AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO logtable VALUES ( user, sysdate, p_message );
COMMIT;
END log_message;
BEGIN
Log_message (‘About to insert into temp_table‘);
INSERT INTO temp_table VALUES ( 1);
Log_message (‘ Rollback to insert into temp_table‘);
ROLLBACK;
END;
SELECT * FROM logtable;
SELECT * FROM temp_table;
例17:創建過程,沒有使用自動事務處理進行日誌記錄;
CREATE OR REPLACE PROCEDURE log_message(p_message varchar2)
AS
BEGIN
INSERT INTO logtable VALUES ( user, sysdate, p_message );
COMMIT;
END log_message;
BEGIN
Log_message ( ' About to insert into temp_table ');
INSERT INTO temp_table VALUES ( 1);
Log_message ( ' Rollback to insert into temp_table ');
ROLLBACK;
END;
SELECT * FROM logtable;
SELECT * FROM temp_table;
6.3.5 開發存儲過程步驟
開發存儲過程、函數、包及觸發器的步驟以下:
6.3.5.1 使用文字編輯處理軟件編輯存儲過程源碼
使用文字編輯處理軟件編輯存儲過程源碼,要用相似WORD 文字處理軟件進行編輯時,要將源碼存爲文本格式。
6.3.5.2 在SQLPLUS或用調試工具將存儲過程程序進行解釋
在SQLPLUS或用調試工具將存儲過程程序進行解釋;
在SQL>下調試,可用START 或GET 等ORACLE命令來啓動解釋。如:
SQL>START c:\stat1.sql
若是使用調式工具,可直接編輯和點擊相應的按鈕便可生成存儲過程。
6.3.5.3 調試源碼直到正確
咱們不能保證所寫的存儲過程達到一次就正確。因此這裏的調式是每一個程序員必須進行的工做之一。在SQLPLUS下來調式主要用的方法是:
l 使用 SHOW ERROR命令來提示源碼的錯誤位置;
l 使用 user_errors 數據字典來查看各存儲過程的錯誤位置。
6.3.5.4 受權執行權給相關的用戶或角色
若是調式正確的存儲過程沒有進行受權,那就只有創建者本人才能夠運行。因此做爲應用系統的一部分的存儲過程也必須進行受權才能達到要求。在SQL*PLUS下能夠用GRANT命令來進行存儲過程的運行受權。
GRANT語法:
GRANT system_privilege | role
TO user | role | PUBLIC [ WITH ADMIN OPTION ]
GRANT object_privilege | ALL ON schema.object
TO user | role | PUBLIC [ WITH GRANT OPTION ]
-- 例子:
CREATE OR REPLACE PUBLIC SYNONYM dbms_job FOR dbms_job
GRANT EXECUTE ON dbms_job TO PUBLIC WITH GRANT OPTION
6.3.5.5 與過程相關數據字典
USER_SOURCE, ALL_SOURCE, DBA_SOURCE, USER_ERRORS,
ALL_PROCEDURES,USER_OBJECTS,ALL_OBJECTS,DBA_OBJECTS
相關的權限:
CREATE ANY PROCEDURE
DROP ANY PROCEDURE
在SQL*PLUS 中,能夠用DESCRIBE 命令查看過程的名字及其參數表。
DESC[RIBE] Procedure_name;
6.3.6 刪除過程和函數
1.刪除過程
可使用DROP PROCEDURE命令對不須要的過程進行刪除,語法以下:
DROP PROCEDURE [user.]Procudure_name;
2.刪除函數
可使用DROP FUNCTION 命令對不須要的函數進行刪除,語法以下:
DROP FUNCTION [ user. ]Function_name;
-- 刪除上面實例建立的存儲過程與函數
DROP PROCEDURE logexecution;
DROP PROCEDURE delemp;
DROP PROCEDURE insertemp;
DROP PROCEDURE fireemp;
DROP PROCEDURE queryemp;
DROP PROCEDURE proc_demo;
DROP PROCEDURE log_message;
DROP FUNCTION demo_fun;
DROP FUNCTION get_salary;
6.3.7 過程與函數的比較
使用過程與函數具備以下優勢:
1、共同使用的代碼能夠只須要被編寫和測試一次,而被須要該代碼的任何應用程序(如:.NET、C++、JAVA、VB程序,也能夠是DLL庫)調用。
2、這種集中編寫、集中維護更新、你們共享(或重用)的方法,簡化了應用程序的開發和維護,提升了效率與性能。
3、這種模塊化的方法,使得能夠將一個複雜的問題、大的程序逐步簡化成幾個簡單的、小的程序部分,進行分別編寫、調試。所以使程序的結構清晰、簡單,也容易實現。
4、能夠在各個開發者之間提供處理數據、控制流程、提示信息等方面的一致性。
5、節省內存空間。它們以一種壓縮的形式被存儲在外存中,當被調用時才被放入內存進行處理。而且,若是多個用戶要執行相同的過程或函數時,就只須要在內存中加載一個該過程或函數。
6、提升數據的安全性與完整性。經過把一些對數據的操做放到過程或函數中,就能夠經過是否授予用戶有執行該過程或的權限,來限制某些用戶對數據進行這些操做。
過程與函數的相同功能有:
一、 都使用IN模式的參數傳入數據、OUT模式的參數返回數據。
二、 輸入參數均可以接受默認值,均可以傳值或傳引導。
三、 調用時的實際參數均可以使用位置表示法、名稱表示法或組合方法。
四、 都有聲明部分、執行部分和異常處理部分。
五、 其管理過程都有建立、編譯、受權、刪除、顯示依賴關係等。
使用過程與函數的原則:
1、若是須要返回多個值和不返回值,就使用過程;若是隻須要返回一個值,就使用函數。
2、過程通常用於執行一個指定的動做,函數通常用於計算和返回一個值。
3、能夠SQL語句內部(如表達式)調用函數來完成複雜的計算問題,但不能調用過程。因此這是函數的特點。
7.1 程序包簡介
程序包(PACKAGE,簡稱包)是一組相關過程、函數、變量、常量和遊標等PL/SQL程序設計元素的組合,做爲一個完整的單元存儲在數據庫中,用名稱來標識包。它具備面向對象程序設計語言的特色,是對這些PL/SQL 程序設計元素的封裝。包相似於c#和JAVA語言中的類,其中變量至關於類中的成員變量,過程和函數至關於類方法。把相關的模塊歸類成爲包,可以使開發人員利用面向對象的方法進行存儲過程的開發,從而提升系統性能。
與高級語言中的類相同,包中的程序元素也分爲公用元素和私用元素兩種,這兩種元素的區別是他們容許訪問的程序範圍不一樣,即它們的做用域不一樣。公用元素不只能夠被包中的函數、過程所調用,也能夠被包外的PL/SQL程序訪問,而私有元素只能被包內的函數和過程序所訪問。
固然,對於不包含在程序包中的過程、函數是獨立存在的。通常是先編寫獨立的過程與函數,待其較爲完善或通過充分驗證無誤後,再按邏輯相關性組織爲程序包。
程序包的優勢
u 簡化應用程序設計:程序包的說明部分和包體部分能夠分別建立各編譯。主要體現 在如下三個方面:
1) 能夠在設計一個應用程序時,只建立各編譯程序包的說明部分,而後再編寫引用該 程序包的PL/SQL塊。
2) 當完成整個應用程序的總體框架後,再回頭來定義包體部分。只要不改變包的說明部分,就能夠單獨調試、增長或替換包體的內容,這不會影響其餘的應用程序。
3) 更新包的說明後必須從新編譯引用包的應用程序,但更新包體,則不需從新編譯引用包的應用程序,以快速進行進行應用程序的原形開發。
u 模塊化:可將邏輯相關的PL/SQL塊或元素等組織在一塊兒,用名稱來惟一標識程序 包。把一個大的功能模塊劃分人適當個數小的功能模塊,分別完成各自的功能。這樣組織的程序包都易於編寫,易於理解更易於管理。
u 信息隱藏:由於包中的元素能夠分爲公有元素和私有元素。公有元素可被程序包內的過程、函數等的訪問,還能夠被包外的PL/SQL訪問。但對於私有元素只能被包內的過程、函數等訪問。對於用戶,只需知道包的說明,不用瞭解包休的具體細節。
u 效率高:程序包在應用程序第一次調用程序包中的某個元素時,ORACLE將把整個程序包加載到內存中,當第二次訪問程序包中的元素時,ORACLE將直接從內在中讀取,而不須要進行磁盤I/O操做而影響速度,同時位於內在中的程序包可被同一會話期間的其它應用程序共享。所以,程序包增長了重用性並改善了多用戶、多應用程序環境的效率。
對程序包的優勢可總結以下:在PL/SQL程序設計中,使用包不只可使程序設計模塊化,對外隱藏包內所使用的信息(經過使用私用變量),而寫能夠提升程序的執行效率。由於,當程序首次調用包內函數或過程時,ORACLE將整個包調入內存,當再次訪問包內元素時,ORACLE直接從內存中讀取,而不須要進行磁盤I/O操做,從而使程序執行效率獲得提升。
一個包由兩個分開的部分組成:
包說明(PACKAGE):包說明部分聲明包內數據類型、變量、常量、遊標、子程序和異常錯誤處理等元素,這些元素爲包的公有元素。
包主體(PACKAGE BODY):包主體則是包定義部分的具體實現,它定義了包定義部分所聲明的遊標和子程序,在包主體中還能夠聲明包的私有元素。
包說明和包主體分開編譯,並做爲兩部分分開的對象存放在數據庫字典中,可查看數據字典user_source, all_source, dba_source,分別瞭解包說明與包主體的詳細信息。
7.2 程序包的定義
程序包的定義分爲程序包說明定義和程序包主體定義兩部分組成。
程 序包說明用於聲明包的公用組件,如變量、常量、自定義數據類型、異常、過程、函數、遊標等。包說明中定義的公有組件不只能夠在包內使用,還能夠由包外其餘 過程、函數。但須要說明與注意的是,咱們爲了實現信息的隱藏,建議不要將全部組件都放在包說明處聲明,只應把公共組件放在包聲明部分。包的名稱是惟一的, 但對於兩個包中的公有組件的名稱能夠相同,這種用「包名.公有組件名「加以區分。
包體是包的具體實現細節,其實如今包說明中聲明的全部公有過程、函數、遊標等。固然也能夠在包體中聲明僅屬於本身的私有過程、函數、遊標等。建立包體時,有如下幾點須要注意:
u 包體只能在包說明被建立或編譯後才能進行建立或編譯。
u 在包體中實現的過程、函數、遊標的名稱必須與包說明中的過程、函數、遊標一致,包括名稱、參數的名稱以及參數的模式(IN、OUT、IN OUT)。並建設按包說明中的次序定義包體中具體的實現。
u 在包體中聲明的數據類型、變量、常量都是私有的,只能在包體中使用而不能被印刷體外的應用程序訪問與使用。
u 在包體執行部分,可對包說明,包體中聲明的公有或私有變量進行初始化或其它設置。
建立程序包說明語法格式:
CREATE [ OR REPLACE ] PACKAGE package_name
[ AUTHID {CURRENT_USER | DEFINER} ]
{ IS | AS}
[ 公有數據類型定義[公有數據類型定義 ]…]
[ 公有遊標聲明[公有遊標聲明 ]…]
[ 公有變量、常量聲明[公有變量、常量聲明 ]…]
[ 公有函數聲明[公有函數聲明 ]…]
[ 公有過程聲明[公有過程聲明 ]…]
END [ package_name ];
其中:AUTHID CURRENT_USER和AUTHID DEFINER選項說明應用程序在調用函數時所使用的權限模式,它們與CREATE FUNCTION語句中invoker_right_clause子句的做用相同。
建立程序包主體語法格式:
CREATE [ OR REPLACE ] PACKAGE BODY package_name
{ IS | AS}
[ 私有數據類型定義[私有數據類型定義 ]…]
[ 私有變量、常量聲明[私有變量、常量聲明 ]…]
[ 私有異常錯誤聲明[私有異常錯誤聲明 ]…]
[ 私有函數聲明和定義[私有函數聲明和定義 ]…]
[ 私有函過程聲明和定義[私有函過程聲明和定義 ]…]
[ 公有遊標定義[公有遊標定義 ]…]
[ 公有函數定義[公有函數定義 ]…]
[ 公有過程定義[公有過程定義 ]…]
BEGIN
執行部分(初始化部分)
END package_name;
其中:在包主體定義公有程序時,它們必須與包定義中所聲明子程序的格式徹底一致。
7.3 包的開發步驟
與開發存儲過程相似,包的開發須要幾個步驟:
1. 將每一個存儲過程調式正確;
2. 用文本編輯軟件將各個存儲過程和函數集成在一塊兒;
3. 按照包的定義要求將集成的文本的前面加上包定義;
4. 按照包的定義要求將集成的文本的前面加上包主體;
5. 使用SQLPLUS或開發工具進行調式。
7.4 包定義的說明
例1:建立的包爲DEMO_PKG, 該包中包含一個記錄變量DEPTREC、兩個函數和一個過程。實現對dept表的增長、刪除與查詢。
CREATE OR REPLACE PACKAGE DEMO_PKG
IS
DEPTREC DEPT %ROWTYPE;
-- Add dept...
FUNCTION add_dept(
dept_no NUMBER,
dept_name VARCHAR2,
location VARCHAR2)
RETURN NUMBER;
-- delete dept...
FUNCTION delete_dept(dept_no NUMBER)
RETURN NUMBER;
-- query dept...
PROCEDURE query_dept(dept_no IN NUMBER);
END DEMO_PKG;
包主體的建立方法,它實現上面所聲明的包定義,並在包主體中聲明一個私有變量flag和一個私有函數check_dept,因爲在add_dept和remove_dept等函數中須要調用check_dpet函數,因此,在定義check_dept 函數以前首先對該函數進行聲明,這種聲明方法稱做前向聲明。
CREATE OR REPLACE PACKAGE BODY DEMO_PKG
IS
FUNCTION add_dept
(
dept_no NUMBER,
dept_name VARCHAR2,
location VARCHAR2
)
RETURN NUMBER
IS
empno_remaining EXCEPTION; -- 自定義異常
PRAGMA EXCEPTION_INIT(empno_remaining, - 1);
/* -1 是違反惟一約束條件的錯誤代碼 */
BEGIN
INSERT INTO dept VALUES(dept_no, dept_name, location);
IF SQL %FOUND THEN
RETURN 1;
END IF;
EXCEPTION
WHEN empno_remaining THEN
RETURN 0;
WHEN OTHERS THEN
RETURN - 1;
END add_dept;
FUNCTION delete_dept(dept_no NUMBER)
RETURN NUMBER
IS
BEGIN
DELETE FROM dept WHERE deptno = dept_no;
IF SQL %FOUND THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
EXCEPTION
WHEN OTHERS THEN
RETURN - 1;
END delete_dept;
PROCEDURE query_dept
(dept_no IN NUMBER)
IS
BEGIN
SELECT * INTO DeptRec FROM dept WHERE deptno =dept_no;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:數據庫中沒有編碼爲 ' ||dept_no || ' 的部門 ');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE( ' 程序運行錯誤,請使用遊標進行操做! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' ---- ' ||SQLERRM);
END query_dept;
BEGIN
Null;
END DEMO_PKG;
對包內共有元素的調用格式爲:包名.元素名稱
調用DEMO_PKG包內函數對dept表進行插入、查詢和刪除操做,並經過DEMO_PKG包中的記錄變量DEPTREC顯示所查詢到的數據庫信息:
DECLARE
Var NUMBER;
BEGIN
Var : = DEMO_PKG.add_dept( 90, ' HKLORB ', ' HAIKOU ');
IF var =- 1 THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' ---- ' ||SQLERRM);
ELSIF var = 0 THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:該部門記錄已經存在! ');
ELSE
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:添加記錄成功! ');
DEMO_PKG.query_dept( 90);
DBMS_OUTPUT.PUT_LINE(DEMO_PKG.DeptRec.deptno || ' --- ' ||
DEMO_PKG.DeptRec.dname || ' --- ' ||DEMO_PKG.DeptRec.loc);
var : = DEMO_PKG.delete_dept( 90);
IF var =- 1 THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' ---- ' ||SQLERRM);
ELSIF var = 0 THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:該部門記錄不存在! ');
ELSE
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:刪除記錄成功! ');
END IF;
END IF;
END;
例2: 建立包EMP_PKG,讀取emp表中的數據
-- 建立包說明
CREATE OR REPLACE PACKAGE EMP_PKG
IS
TYPE emp_table_type IS TABLE OF emp %ROWTYPE
INDEX BY BINARY_INTEGER;
PROCEDURE read_emp_table (p_emp_table OUT emp_table_type);
END EMP_PKG;
-- 建立包體
CREATE OR REPLACE PACKAGE BODY EMP_PKG
IS
PROCEDURE read_emp_table (p_emp_table OUT emp_table_type)
IS
I BINARY_INTEGER : = 0;
BEGIN
FOR emp_record IN ( SELECT * FROM emp ) LOOP
P_emp_table(i) : = emp_record;
I : = I + 1;
END LOOP;
END read_emp_table;
END EMP_PKG;
-- 執行
DECLARE
E_table EMP_PKG.emp_table_type;
BEGIN
EMP_PKG.read_emp_table(e_table);
FOR I IN e_table.FIRST ..e_table.LAST LOOP
DBMS_OUTPUT.PUT_LINE(e_table(i).empno || ' ' ||e_table(i).ename);
END LOOP;
END;
例3: 建立包MANAGE_EMP_PKG,對員工進行管理(新增員工、新增部門、刪除指定員工、刪除指定部門、增長指定員工的工資與獎金):
-- 建立序列從100開始,依次增長1
CREATE SEQUENCE empseq
START WITH 100
INCREMENT BY 1
ORDER NOCYCLE;
-- 建立序列從100開始,依次增長10
CREATE SEQUENCE deptseq
START WITH 100
INCREMENT BY 10
ORDER NOCYCLE;
-- *******************************************
-- 建立包說明
-- 包 名:MANAGE_EMP_PKG
-- 功能描述:對員工進行管理(新增員工,新增部門
-- ,刪除員工,刪除部門,增長工資與獎金等)
-- 建立人員:胡勇
-- 建立日期:2010-05-19
-- Q Q: 80368704
-- E-mail : 80368704@yahoo.com.cn
-- WebSite: http://www.cnblogs.com/huyong
-- ******************************************
CREATE OR REPLACE PACKAGE MANAGE_EMP_PKG
AS
-- 增長一名員工
FUNCTION hire_emp
(ename VARCHAR2, job VARCHAR2
, mgr NUMBER, sal NUMBER
, comm NUMBER, deptno NUMBER)
RETURN NUMBER;
-- 新增一個部門
FUNCTION add_dept(dname VARCHAR2, loc VARCHAR2)
RETURN NUMBER;
-- 刪除指定員工
PROCEDURE remove_emp(empno NUMBER);
-- 刪除指定部門
PROCEDURE remove_dept(deptno NUMBER);
-- 增長指定員工的工資
PROCEDURE increase_sal(empno NUMBER, sal_incr NUMBER);
-- 增長指定員工的獎金
PROCEDURE increase_comm(empno NUMBER, comm_incr NUMBER);
END MANAGE_EMP_PKG; -- 建立包說明結束
-- *******************************************
-- 建立包體
-- 包 名:MANAGE_EMP_PKG
-- 功能描述:對員工進行管理(新增員工,新增部門
-- ,刪除員工,刪除部門,增長工資與獎金等)
-- 建立人員:胡勇
-- 建立日期:2010-05-19
-- Q Q: 80368704
-- E-mail : 80368704@yahoo.com.cn
-- WebSite: http://www.cnblogs.com/huyong
-- ******************************************
CREATE OR REPLACE PACKAGE BODY MANAGE_EMP_PKG
AS
total_emps NUMBER; -- 員工數
total_depts NUMBER; -- 部門數
no_sal EXCEPTION;
no_comm EXCEPTION;
-- 增長一名員工
FUNCTION hire_emp(ename VARCHAR2, job VARCHAR2, mgr NUMBER,
sal NUMBER, comm NUMBER, deptno NUMBER)
RETURN NUMBER -- 返回新增長的員工編號
IS
new_empno NUMBER( 4);
BEGIN
SELECT empseq.NEXTVAL INTO new_empno FROM dual;
SELECT COUNT( *) INTO total_emps FROM emp; -- 當前記錄總數
INSERT INTO emp
VALUES (new_empno, ename, job, mgr, sysdate, sal, comm, deptno);
total_emps: =total_emps + 1;
RETURN(new_empno);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:發生系統錯誤! ');
END hire_emp;
-- 新增一個部門
FUNCTION add_dept(dname VARCHAR2, loc VARCHAR2)
RETURN NUMBER
IS
new_deptno NUMBER( 4); -- 部門編號
BEGIN
-- 獲得一個新的自增的員工編號
SELECT deptseq.NEXTVAL INTO new_deptno FROM dual;
SELECT COUNT( *) INTO total_depts FROM dept; -- 當前部門總數
INSERT INTO dept VALUES (new_deptno, dname, loc);
total_depts: =total_depts;
RETURN(new_deptno);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:發生系統錯誤! ');
END add_dept;
-- 刪除指定員工
PROCEDURE remove_emp(empno NUMBER)
IS
no_result EXCEPTION; -- 自定義異常
BEGIN
DELETE FROM emp WHERE emp.empno =remove_emp.empno;
IF SQL %NOTFOUND THEN
RAISE no_result;
END IF;
total_emps: =total_emps - 1; -- 總的員工數減1
EXCEPTION
WHEN no_result THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:你須要的數據不存在! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:發生系統錯誤! ');
END remove_emp;
-- 刪除指定部門
PROCEDURE remove_dept(deptno NUMBER)
IS
no_result EXCEPTION; -- 自定義異常
exception_deptno_remaining EXCEPTION; -- 自定義異常
/* -2292 是違反一致性約束的錯誤代碼 */
PRAGMA EXCEPTION_INIT(exception_deptno_remaining, - 2292);
BEGIN
DELETE FROM dept WHERE dept.deptno =remove_dept.deptno;
IF SQL %NOTFOUND THEN
RAISE no_result;
END IF;
total_depts: =total_depts - 1; -- 總的部門數減1
EXCEPTION
WHEN no_result THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:你須要的數據不存在! ');
WHEN exception_deptno_remaining THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:違反數據完整性約束! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:發生系統錯誤! ');
END remove_dept;
-- 給指定員工增長指定數量的工資
PROCEDURE increase_sal(empno NUMBER, sal_incr NUMBER)
IS
curr_sal NUMBER( 7, 2); -- 當前工資
BEGIN
-- 獲得當前工資
SELECT sal INTO curr_sal FROM emp WHERE emp.empno =increase_sal.empno;
IF curr_sal IS NULL THEN
RAISE no_sal;
ELSE
UPDATE emp SET sal = sal + increase_sal.sal_incr -- 當前工資加新增的工資
WHERE emp.empno = increase_sal.empno;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:你須要的數據不存在! ');
WHEN no_sal THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:此員工的工資不存在! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:發生系統錯誤! ');
END increase_sal;
-- 給指定員工增長指定數量的獎金
PROCEDURE increase_comm(empno NUMBER, comm_incr NUMBER)
IS
curr_comm NUMBER( 7, 2);
BEGIN
-- 獲得指定員工的當前資金
SELECT comm INTO curr_comm
FROM emp WHERE emp.empno = increase_comm.empno;
IF curr_comm IS NULL THEN
RAISE no_comm;
ELSE
UPDATE emp SET comm = comm + increase_comm.comm_incr
WHERE emp.empno =increase_comm.empno;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:你須要的數據不存在! ');
WHEN no_comm THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:此員工的獎金不存在! ');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( ' 舒適提示:發生系統錯誤! ');
END increase_comm;
END MANAGE_EMP_PKG; -- 建立包體結束
-- 調用
SQL > variable empno number
SQL > execute :empno: = manage_emp_pkg.hire_emp( ' HUYONG ',PM, 1455, 5500, 14, 10)
PL /SQL procedure successfully completed
empno
-- -------
105
例4:利用遊標變量建立包 CURROR_VARIBAL_PKG。因爲遊標變量指是一個指針,其狀態是不肯定的,所以它不能隨同包存儲在數據庫中,既不能在PL/SQL包中聲明遊標變量。但在包中能夠建立遊標變量參照類型,並可向包中的子程序傳遞遊標變量參數。
-- *******************************************
-- 建立包體
-- 包 名:CURROR_VARIBAL_PKG
-- 功能描述:在包中引用遊標變量
-- 建立人員:胡勇
-- 建立日期:2010-05-19
-- Q Q: 80368704
-- E-mail : 80368704@yahoo.com.cn
-- WebSite: http://www.cnblogs.com/huyong
-- ******************************************
CREATE OR REPLACE PACKAGE CURROR_VARIBAL_PKG AS
TYPE DeptCurType IS REF CURSOR
RETURN dept %ROWTYPE; -- 強類型定義
TYPE CurType IS REF CURSOR; -- 弱類型定義
PROCEDURE OpenDeptVar(
Cv IN OUT DeptCurType,
Choice INTEGER DEFAULT 0,
Dept_no NUMBER DEFAULT 50,
Dept_name VARCHAR DEFAULT ' % ');
END;
-- *******************************************
-- 建立包體
-- 包 名:CURROR_VARIBAL_PKG
-- 功能描述:在包中引用遊標變量
-- 建立人員:胡勇
-- 建立日期:2010-05-19
-- Q Q: 80368704
-- E-mail : 80368704@yahoo.com.cn
-- WebSite: http://www.cnblogs.com/huyong
-- ******************************************
CREATE OR REPLACE PACKAGE BODY CURROR_VARIBAL_PKG
AS
PROCEDURE OpenDeptvar(
Cv IN OUT DeptCurType,
Choice INTEGER DEFAULT 0,
Dept_no NUMBER DEFAULT 50,
Dept_name VARCHAR DEFAULT ‘ %’)
IS
BEGIN
IF choice = 1 THEN
OPEN cv FOR SELECT * FROM dept WHERE deptno <= dept_no;
ELSIF choice = 2 THEN
OPEN cv FOR SELECT * FROM dept WHERE dname LIKE dept_name;
ELSE
OPEN cv FOR SELECT * FROM dept;
END IF;
END OpenDeptvar;
END CURROR_VARIBAL_PKG;
-- 定義一個過程
CREATE OR REPLACE PROCEDURE UP_OpenCurType(
Cv IN OUT CURROR_VARIBAL_PKG.CurType,
FirstCapInTableName CHAR)
AS
BEGIN
-- CURROR_VARIBAL_PKG.CurType採用弱類型定義
-- 因此可使用它定義的遊標變量打開不一樣類型的查詢語句
IF FirstCapInTableName = ' D ' THEN
OPEN cv FOR SELECT * FROM dept;
ELSE
OPEN cv FOR SELECT * FROM emp;
END IF;
END UP_OpenCurType;
-- 定義一個應用
DECLARE
DeptRec Dept %ROWTYPE;
EmpRec Emp %ROWTYPE;
Cv1 CURROR_VARIBAL_PKG.deptcurtype;
Cv2 CURROR_VARIBAL_PKG.curtype;
BEGIN
DBMS_OUTPUT.PUT_LINE( ' 遊標變量強類型定義應用 ');
CURROR_VARIBAL_PKG.OpenDeptVar(cv1, 1, 30);
FETCH cv1 INTO DeptRec;
WHILE cv1 %FOUND LOOP
DBMS_OUTPUT.PUT_LINE(DeptRec.deptno || ' : ' ||DeptRec.dname);
FETCH cv1 INTO DeptRec;
END LOOP;
CLOSE cv1;
DBMS_OUTPUT.PUT_LINE( ' 遊標變量弱類型定義應用 ');
CURROR_VARIBAL_PKG.OpenDeptvar(cv2, 2, dept_name => ' A% ');
FETCH cv2 INTO DeptRec;
WHILE cv2 %FOUND LOOP
DBMS_OUTPUT.PUT_LINE(DeptRec.deptno || ' : ' ||DeptRec.dname);
FETCH cv2 INTO DeptRec;
END LOOP;
DBMS_OUTPUT.PUT_LINE( ' 遊標變量弱類型定義應用—dept表 ');
UP_OpenCurType(cv2, ' D ');
FETCH cv2 INTO DeptRec;
WHILE cv2 %FOUND LOOP
DBMS_OUTPUT.PUT_LINE(deptrec.deptno || ' : ' ||deptrec.dname);
FETCH cv2 INTO deptrec;
END LOOP;
DBMS_OUTPUT.PUT_LINE( ' 遊標變量弱類型定義應用—emp表 ');
UP_OpenCurType(cv2, ' E ');
FETCH cv2 INTO EmpRec;
WHILE cv2 %FOUND LOOP
DBMS_OUTPUT.PUT_LINE(emprec.empno || ' : ' ||emprec.ename);
FETCH cv2 INTO emprec;
END LOOP;
CLOSE cv2;
END;
-- --------運行結果-------------------
遊標變量強類型定義應用
10:ACCOUNTING
20:RESEARCH
30:SALES
遊標變量弱類型定義應用
10:ACCOUNTING
遊標變量弱類型定義應用—dept表
10:ACCOUNTING
20:RESEARCH
30:SALES
40:OPERATIONS
50:50abc
60:Developer
遊標變量弱類型定義應用—emp表
7369:SMITH
7499:ALLEN
7521:WARD
7566:JONES
7654:MARTIN
7698:BLAKE
7782:CLARK
7788:SCOTT
7839:KING
7844:TURNER
7876:ADAMS
7900:JAMES
7902:FORD
7934:MILLER
PL /SQL procedure successfully completed
7.5 子程序重載
PL/SQL 容許對包內子程序和本地子程序進行重載。所謂重載時指兩個或多個子程序有相同的名稱,但擁有不一樣的參數變量、參數順序或參數數據類型。
例5:
-- *******************************************
-- 建立包說明
-- 包 名:DEMO_PKG1
-- 功能描述:建立包對子程序重載進行測試
-- 建立人員:胡勇
-- 建立日期:2010-05-22
-- Q Q: 80368704
-- E-mail : 80368704@yahoo.com.cn
-- WebSite: http://www.cnblogs.com/huyong
-- ******************************************
CREATE OR REPLACE PACKAGE DEMO_PKG1
IS
DeptRec dept %ROWTYPE;
V_sqlcode NUMBER;
V_sqlerr VARCHAR2( 2048);
-- 兩個子程序名字相同,但參數類型不一樣
FUNCTION query_dept(dept_no IN NUMBER)
RETURN INTEGER;
FUNCTION query_dept(dept_no IN VARCHAR2)
RETURN INTEGER;
END DEMO_PKG1;
-- *******************************************
-- 建立包體
-- 包 名:DEMO_PKG1
-- 功能描述:建立包對子程序重載進行測試
-- 建立人員:胡勇
-- 建立日期:2010-05-22
-- Q Q: 80368704
-- E-mail : 80368704@yahoo.com.cn
-- WebSite: http://www.cnblogs.com/huyong
-- ******************************************
CREATE OR REPLACE PACKAGE BODY DEMO_PKG1
IS
FUNCTION check_dept(dept_no NUMBER)
RETURN INTEGER
IS
deptCnt INTEGER; -- 指定部門號的部門數量
BEGIN
SELECT COUNT( *) INTO deptCnt FROM dept WHERE deptno = dept_no;
IF deptCnt > 0 THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END check_dept;
FUNCTION check_dept(dept_no VARCHAR2)
RETURN INTEGER
IS
deptCnt INTEGER;
BEGIN
SELECT COUNT( *) INTO deptCnt FROM dept WHERE deptno =dept_no;
IF deptCnt > 0 THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END check_dept;
FUNCTION query_dept(dept_no IN NUMBER)
RETURN INTEGER
IS
BEGIN
IF check_dept(dept_no) = 1 THEN
SELECT * INTO DeptRec FROM dept WHERE deptno =dept_no;
RETURN 1;
ELSE
RETURN 0;
END IF;
END query_dept;
FUNCTION query_dept(dept_no IN VARCHAR2)
RETURN INTEGER
IS
BEGIN
IF check_dept(dept_no) = 1 THEN
SELECT * INTO DeptRec FROM dept WHERE deptno = dept_no;
RETURN 1;
ELSE
RETURN 0;
END IF;
END query_dept;
END DEMO_PKG1;
7.6 加密實用程序
ORACLE 提供了一個實用工具來加密或者包裝用戶的PL/SQL,它會將用戶的PL/SQL改變爲只有ORACLE可以解釋的代碼版本.
WRAP 實用工具位於$ORACLE_HOME/BIN.
格式爲:
WRAP INAME=<input_file_name> [ONAME=<output_file_name>]
wrap iname=e:/sample.txt
注意:在加密前,請將PL/SQL程序先保存一份,以備後用。
7.7 刪除包
可使用 DROP PACKAGE 命令對不須要的包進行刪除,語法以下:
DROP PACKAGE [ BODY ] [ user. ]package_name;
DROP PROCEDURE OpenCurType; -- 刪除存儲過程
-- 刪除咱們實例中建立的各個包
DROP PACKAGE demo_pack;
DROP PACKAGE demo_pack1;
DROP PACKAGE emp_mgmt;
DROP PACKAGE emp_package;
7.8 包的管理
包與過程、函數同樣,也是存儲在數據庫中的,能夠隨時查看其源碼。如有須要,在建立包時能夠隨時查看更詳細的編譯錯誤。不須要的包也能夠刪除。
一樣,爲了不調用的失敗,在更新表的結構後,必定要記得從新編譯依賴於它的程序包。在更新了包說明或包體後,也應該從新編譯包說明與包體。語法以下:
ALTER PACKAGE package_name COMPILE [ PACKAGE|BODY|SPECIFICATION ];
也能夠經過如下數據字典視圖查看包的相關。
DBA_SOURCE, USER_SOURCE, USER_ERRORS, DBA-OBJECTS
如,咱們能夠用:select text from user_source where name = 'DEMO_PKG1';來查看咱們建立的包的源碼。
觸發器是許多關係數據庫系統都提供的一項技術。在ORACLE系統裏,觸發器相似過程和函數,都有聲明,執行和異常處理過程的PL/SQL塊。
8.1 觸發器類型
觸發器在數據庫裏以獨立的對象存儲,它與存儲過程和函數不一樣的是,存儲過程與函數須要用戶顯示調用才執行,而觸發器是由一個事件來啓動運行。即觸發器是當某個事件發生時自動地隱式運行。而且,觸發器不能接收參數。因此運行觸發器就叫觸發或點火(firing)。ORACLE事件指的是對數據庫的表進行的INSERT、UPDATE及DELETE操做或對視圖進行相似的操做。ORACLE將觸發器的功能擴展到了觸發ORACLE,如數據庫的啓動與關閉等。因此觸發器經常使用來完成由數據庫的完整性約束難以完成的複雜業務規則的約束,或用來監視對數據庫的各類操做,實現審計的功能。
8.1.1 DML觸發器
ORACLE能夠在DML語句進行觸發,能夠在DML操做前或操做後進行觸發,而且能夠對每一個行或語句操做上進行觸發。
8.1.2 替代觸發器
因爲在ORACLE裏,不能直接對由兩個以上的表創建的視圖進行操做。因此給出了替代觸發器。它就是ORACLE 8專門爲進行視圖操做的一種處理方法。
8.1.3 系統觸發器
ORACLE 8i 提供了第三種類型的觸發器叫系統觸發器。它能夠在ORACLE數據庫系統的事件中進行觸發,如ORACLE系統的啓動與關閉等。
觸發器組成:
l 觸發事件:引發觸發器被觸發的事件。 例如:DML語句(INSERT, UPDATE, DELETE語句對錶或視圖執行數據處理操做)、DDL語句(如CREATE、ALTER、DROP語句在數據庫中建立、修改、刪除模式對象)、數據庫系統事件(如系統啓動或退出、異常錯誤)、用戶事件(如登陸或退出數據庫)。
l 觸發時間:即該TRIGGER 是在觸發事件發生以前(BEFORE)仍是以後(AFTER)觸發,也就是觸發事件和該TRIGGER 的操做順序。
l 觸發操做:即該TRIGGER 被觸發以後的目的和意圖,正是觸發器自己要作的事情。 例如:PL/SQL 塊。
l 觸發對象:包括表、視圖、模式、數據庫。只有在這些對象上發生了符合觸發條件的觸發事件,纔會執行觸發操做。
l 觸發條件:由WHEN子句指定一個邏輯表達式。只有當該表達式的值爲TRUE時,遇到觸發事件纔會自動執行觸發器,使其執行觸發操做。
l 觸發頻率:說明觸發器內定義的動做被執行的次數。即語句級(STATEMENT)觸發器和行級(ROW)觸發器。
語句級(STATEMENT)觸發器:是指當某觸發事件發生時,該觸發器只執行一次;
行級(ROW)觸發器:是指當某觸發事件發生時,對受到該操做影響的每一行數據,觸發器都單獨執行一次。
編寫觸發器時,須要注意如下幾點:
l 觸發器不接受參數。
l 一個表上最多可有12個觸發器,但同一時間、同一事件、同一類型的觸發器只能有一個。並各觸發器之間不能有矛盾。
l 在一個表上的觸發器越多,對在該表上的DML操做的性能影響就越大。
l 觸發器最大爲32KB。若確實須要,能夠先創建過程,而後在觸發器中用CALL語句進行調用。
l 在觸發器的執行部分只能用DML語句(SELECT、INSERT、UPDATE、DELETE),不能使用DDL語句(CREATE、ALTER、DROP)。
l 觸發器中不能包含事務控制語句(COMMIT,ROLLBACK,SAVEPOINT)。由於觸發器是觸發語句的一部分,觸發語句被提交、回退時,觸發器也被提交、回退了。
l 在觸發器主體中調用的任何過程、函數,都不能使用事務控制語句。
l 在觸發器主體中不能申明任何Long和blob變量。新值new和舊值old也不能向表中的任何long和blob列。
l 不一樣類型的觸發器(如DML觸發器、INSTEAD OF觸發器、系統觸發器)的語法格式和做用有較大區別。
8.2 建立觸發器
建立觸發器的通常語法是:
CREATE [ OR REPLACE ] TRIGGER trigger_name
{BEFORE | AFTER }
{ INSERT | DELETE | UPDATE [ OF column [, column … ]]}
[ OR {INSERT | DELETE | UPDATE [OF column [, column … ]]}...]
ON [ schema. ]table_name | [ schema. ]view_name
[ REFERENCING {OLD [AS ] old | NEW [ AS ] new | PARENT as parent}]
[ FOR EACH ROW ]
[ WHEN condition ]
PL /SQL_BLOCK | CALL procedure_name;
其中:
BEFORE 和AFTER指出觸發器的觸發時序分別爲前觸發和後觸發方式,前觸發是在執行觸發事件以前觸發當前所建立的觸發器,後觸發是在執行觸發事件以後觸發當前所建立的觸發器。
FOR EACH ROW選項說明觸發器爲行觸發器。行觸發器和語句觸發器的區別表如今:行觸發器要求當一個DML語句操走影響數據庫中的多行數據時,對於其中的每一個數據行,只要它們符合觸發約束條件,均激活一次觸發器;而語句觸發器將整個語句操做做爲觸發事件,當它符合約束條件時,激活一次觸發器。當省略FOR EACH ROW 選項時,BEFORE 和AFTER 觸發器爲語句觸發器,而INSTEAD OF 觸發器則只能爲行觸發器。
REFERENCING 子句說明相關名稱,在行觸發器的PL/SQL塊和WHEN 子句中可使用相關名稱參照當前的新、舊列值,默認的相關名稱分別爲OLD和NEW。觸發器的PL/SQL塊中應用相關名稱時,必須在它們以前加冒號(:),但在WHEN子句中則不能加冒號。
WHEN 子句說明觸發約束條件。Condition 爲一個邏輯表達時,其中必須包含相關名稱,而不能包含查詢語句,也不能調用PL/SQL 函數。WHEN 子句指定的觸發約束條件只能用在BEFORE 和AFTER 行觸發器中,不能用在INSTEAD OF 行觸發器和其它類型的觸發器中。
當一個基表被修改( INSERT, UPDATE, DELETE)時要執行的存儲過程,執行時根據其所依附的基表改動而自動觸發,所以與應用程序無關,用數據庫觸發器能夠保證數據的一致性和完整性。
每張表最多可創建12 種類型的觸發器,它們是:
BEFORE INSERT
BEFORE INSERT FOR EACH ROW
AFTER INSERT
AFTER INSERT FOR EACH ROW
BEFORE UPDATE
BEFORE UPDATE FOR EACH ROW
AFTER UPDATE
AFTER UPDATE FOR EACH ROW
BEFORE DELETE
BEFORE DELETE FOR EACH ROW
AFTER DELETE
AFTER DELETE FOR EACH ROW
8.2.1 觸發器觸發次序
1. 執行 BEFORE語句級觸發器;
2. 對與受語句影響的每一行:
l 執行 BEFORE行級觸發器
l 執行 DML語句
l 執行 AFTER行級觸發器
3. 執行 AFTER語句級觸發器
8.2.2 建立DML觸發器
觸發器名與過程名和包的名字不同,它是單獨的名字空間,於是觸發器名能夠和表或過程有相同的名字,但在一個模式中觸發器名不能相同。
DML觸發器的限制
l CREATE TRIGGER語句文本的字符長度不能超過32KB;
l 觸發器體內的SELECT 語句只能爲SELECT … INTO …結構,或者爲定義遊標所使用的SELECT 語句。
l 觸發器中不能使用數據庫事務控制語句 COMMIT; ROLLBACK, SVAEPOINT 語句;
l 由觸發器所調用的過程或函數也不能使用數據庫事務控制語句;
l 觸發器中不能使用LONG, LONG RAW 類型;
l 觸發器內能夠參照LOB 類型列的列值,但不能經過 :NEW 修改LOB列中的數據;
DML觸發器基本要點
l 觸發時機:指定觸發器的觸發時間。若是指定爲BEFORE,則表示在執行DML操做以前觸發,以便防止某些錯誤操做發生或實現某些業務規則;若是指定爲AFTER,則表示在執行DML操做以後觸發,以便記錄該操做或作某些過後處理。
l 觸發事件:引發觸發器被觸發的事件,即DML操做(INSERT、UPDATE、DELETE)。既能夠是單個觸發事件,也能夠是多個觸發事件的組合(只能使用OR邏輯組合,不能使用AND邏輯組合)。
l 條件謂詞:當在觸發器中包含多個觸發事件(INSERT、UPDATE、DELETE)的組合時,爲了分別針對不一樣的事件進行不一樣的處理,須要使用ORACLE提供的以下條件謂詞。
1)。INSERTING:當觸發事件是INSERT時,取值爲TRUE,不然爲FALSE。
2)。UPDATING [(column_1,column_2,…,column_x)]:當觸發事件是UPDATE 時,若是修改了column_x列,則取值爲TRUE,不然爲FALSE。其中column_x是可選的。
3)。DELETING:當觸發事件是DELETE時,則取值爲TRUE,不然爲FALSE。
解發對象:指定觸發器是建立在哪一個表、視圖上。
l 觸發類型:是語句級仍是行級觸發器。
l 觸發條件:由WHEN子句指定一個邏輯表達式,只容許在行級觸發器上指定觸發條件,指定UPDATING後面的列的列表。
問題:當觸發器被觸發時,要使用被插入、更新或刪除的記錄中的列值,有時要使用操做前、 後列的值.
實現: :NEW 修飾符訪問操做完成後列的值
:OLD 修飾符訪問操做完成前列的值
特性 |
INSERT |
UPDATE |
DELETE |
OLD |
NULL |
實際值 |
實際值 |
NEW |
實際值 |
實際值 |
NULL |
例1: 創建一個觸發器, 當職工表 emp 表被刪除一條記錄時,把被刪除記錄寫到職工表刪除日誌表中去。
CREATE TABLE emp_his AS SELECT * FROM EMP WHERE 1 = 2;
CREATE OR REPLACE TRIGGER tr_del_emp
BEFORE DELETE -- 指定觸發時機爲刪除操做前觸發
ON scott.emp
FOR EACH ROW -- 說明建立的是行級觸發器
BEGIN
-- 將修改前數據插入到日誌記錄表 del_emp ,以供監督使用。
INSERT INTO emp_his(deptno , empno, ename , job ,mgr , sal , comm , hiredate )
VALUES( :old.deptno, :old.empno, :old.ename , :old.job,:old.mgr, :old.sal, :old.comm, :old.hiredate );
END;
DELETE emp WHERE empno = 7788;
DROP TABLE emp_his;
DROP TRIGGER del_emp;
例2:限制對Departments表修改(包括INSERT,DELETE,UPDATE)的時間範圍,即不容許在非工做時間修改departments表。
CREATE OR REPLACE TRIGGER tr_dept_time
BEFORE INSERT OR DELETE OR UPDATE
ON departments
BEGIN
IF (TO_CHAR(sysdate, ' DAY ') IN ( ' 星期六 ', ' 星期日 ')) OR (TO_CHAR(sysdate, ' HH24:MI ') NOT BETWEEN ' 08:30 ' AND ' 18:00 ') THEN
RAISE_APPLICATION_ERROR( - 20001, ' 不是上班時間,不能修改departments表 ');
END IF;
END;
例3:限定只對部門號爲80的記錄進行行觸發器操做。
CREATE OR REPLACE TRIGGER tr_emp_sal_comm
BEFORE UPDATE OF salary, commission_pct
OR DELETE
ON HR.employees
FOR EACH ROW
WHEN (old.department_id = 80)
BEGIN
CASE
WHEN UPDATING ( ' salary ') THEN
IF :NEW.salary < :old.salary THEN
RAISE_APPLICATION_ERROR( - 20001, ' 部門80的人員的工資不能降 ');
END IF;
WHEN UPDATING ( ' commission_pct ') THEN
IF :NEW.commission_pct < :old.commission_pct THEN
RAISE_APPLICATION_ERROR( - 20002, ' 部門80的人員的獎金不能降 ');
END IF;
WHEN DELETING THEN
RAISE_APPLICATION_ERROR( - 20003, ' 不能刪除部門80的人員記錄 ');
END CASE;
END;
/*
實例:
UPDATE employees SET salary = 8000 WHERE employee_id = 177;
DELETE FROM employees WHERE employee_id in (177,170);
*/
例4:利用行觸發器實現級聯更新。在修改了主表regions中的region_id以後(AFTER),級聯的、自動的更新子表countries表中原來在該地區的國家的region_id。
CREATE OR REPLACE TRIGGER tr_reg_cou
AFTER update OF region_id
ON regions
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE( ' 舊的region_id值是 ' ||:old.region_id
|| ' 、新的region_id值是 ' ||:new.region_id);
UPDATE countries SET region_id = :new.region_id
WHERE region_id = :old.region_id;
END;
例5:在觸發器中調用過程。
CREATE OR REPLACE PROCEDURE add_job_history
( p_emp_id job_history.employee_id %type
, p_start_date job_history.start_date %type
, p_end_date job_history.end_date %type
, p_job_id job_history.job_id %type
, p_department_id job_history.department_id %type
)
IS
BEGIN
INSERT INTO job_history (employee_id, start_date, end_date,
job_id, department_id)
VALUES(p_emp_id, p_start_date, p_end_date, p_job_id, p_department_id);
END add_job_history;
-- 建立觸發器調用存儲過程...
CREATE OR REPLACE TRIGGER update_job_history
AFTER UPDATE OF job_id, department_id ON employees
FOR EACH ROW
BEGIN
add_job_history(:old.employee_id, :old.hire_date, sysdate,
:old.job_id, :old.department_id);
END;
8.2.3 建立替代(INSTEAD OF)觸發器
建立觸發器的通常語法是:
CREATE [ OR REPLACE ] TRIGGER trigger_name
INSTEAD OF
{ INSERT | DELETE | UPDATE [ OF column [, column … ]]}
[ OR {INSERT | DELETE | UPDATE [OF column [, column … ]]}...]
ON [ schema. ] view_name -- 只能定義在視圖上
[ REFERENCING {OLD [AS ] old | NEW [ AS ] new | PARENT as parent}]
[ FOR EACH ROW ] -- 由於INSTEAD OF觸發器只能在行級上觸發,因此沒有必要指定
[ WHEN condition ]
PL /SQL_block | CALL procedure_name;
其中:
INSTEAD OF 選項使ORACLE激活觸發器,而不執行觸發事件。只能對視圖和對象視圖創建INSTEAD OF觸發器,而不能對錶、模式和數據庫創建INSTEAD OF 觸發器。
FOR EACH ROW選項說明觸發器爲行觸發器。行觸發器和語句觸發器的區別表如今:行觸發器要求當一個DML語句操走影響數據庫中的多行數據時,對於其中的每一個數據行,只要它們符合觸發約束條件,均激活一次觸發器;而語句觸發器將整個語句操做做爲觸發事件,當它符合約束條件時,激活一次觸發器。當省略FOR EACH ROW 選項時,BEFORE 和AFTER 觸發器爲語句觸發器,而INSTEAD OF 觸發器則爲行觸發器。
REFERENCING 子句說明相關名稱,在行觸發器的PL/SQL塊和WHEN 子句中可使用相關名稱參照當前的新、舊列值,默認的相關名稱分別爲OLD和NEW。觸發器的PL/SQL塊中應用相關名稱時,必須在它們以前加冒號(:),但在WHEN子句中則不能加冒號。
WHEN 子句說明觸發約束條件。Condition 爲一個邏輯表達時,其中必須包含相關名稱,而不能包含查詢語句,也不能調用PL/SQL 函數。WHEN 子句指定的觸發約束條件只能用在BEFORE 和AFTER 行觸發器中,不能用在INSTEAD OF 行觸發器和其它類型的觸發器中。
INSTEAD_OF 用於對視圖的DML觸發,因爲視圖有多是由多個表進行聯結(join)而成,於是並不是是全部的聯結都是可更新的。但能夠按照所需的方式執行更新,例以下面狀況:
例1:
CREATE OR REPLACE VIEW emp_view AS
SELECT deptno, count( *) total_employeer, sum(sal) total_salary
FROM emp GROUP BY deptno;
在此視圖中直接刪除是非法:
SQL > DELETE FROM emp_view WHERE deptno = 10;
DELETE FROM emp_view WHERE deptno = 10
ERROR 位於第 1 行:
ORA-01732: 此視圖的數據操縱操做非法
可是咱們能夠建立INSTEAD_OF觸發器來爲 DELETE 操做執行所需的處理,即刪除EMP表中全部基準行:
CREATE OR REPLACE TRIGGER emp_view_delete
INSTEAD OF DELETE ON emp_view FOR EACH ROW
BEGIN
DELETE FROM emp WHERE deptno = :old.deptno;
END emp_view_delete;
DELETE FROM emp_view WHERE deptno = 10;
DROP TRIGGER emp_view_delete;
DROP VIEW emp_view;
例2:建立複雜視圖,針對INSERT操做建立INSTEAD OF觸發器,向複雜視圖插入數據。
l 建立視圖:
CREATE OR REPLACE FORCE VIEW "HR"."V_REG_COU" ("R_ID", "R_NAME", "C_ID", "C_NAME")
AS
SELECT r.region_id,
r.region_name,
c.country_id,
c.country_name
FROM regions r,
countries c
WHERE r.region_id = c.region_id;
l 建立觸發器:
CREATE OR REPLACE TRIGGER "HR"."TR_I_O_REG_COU" INSTEAD OF
INSERT ON v_reg_cou FOR EACH ROW DECLARE v_count NUMBER;
BEGIN
SELECT COUNT( *) INTO v_count FROM regions WHERE region_id = :new.r_id;
IF v_count = 0 THEN
INSERT INTO regions
(region_id, region_name
) VALUES
(:new.r_id, :new.r_name
);
END IF;
SELECT COUNT( *) INTO v_count FROM countries WHERE country_id = :new.c_id;
IF v_count = 0 THEN
INSERT
INTO countries
(
country_id,
country_name,
region_id
)
VALUES
(
:new.c_id,
:new.c_name,
:new.r_id
);
END IF;
END;
建立INSTEAD OF觸發器須要注意如下幾點:
l 只能被建立在視圖上,而且該視圖沒有指定WITH CHECK OPTION選項。
l 不能指定BEFORE 或 AFTER選項。
l FOR EACH ROW子但是可選的,即INSTEAD OF觸發器只能在行級上觸發、或只能是行級觸發器,沒有必要指定。
l 沒有必要在針對一個表的視圖上建立INSTEAD OF觸發器,只要建立DML觸發器就能夠了。
8.2.3 建立系統事件觸發器
ORACLE10G提供的系統事件觸發器能夠在DDL或數據庫系統上被觸發。DDL指的是數據定義語言,如CREATE 、ALTER及DROP 等。而數據庫系統事件包括數據庫服務器的啓動或關閉,用戶的登陸與退出、數據庫服務錯誤等。建立系統觸發器的語法以下:
建立觸發器的通常語法是:
CREATE OR REPLACE TRIGGER [ sachema. ]trigger_name
{BEFORE |AFTER}
{ddl_event_list | database_event_list}
ON { DATABASE | [ schema. ] SCHEMA }
[ WHEN condition ]
PL /SQL_block | CALL procedure_name;
其中: ddl_event_list:一個或多個DDL 事件,事件間用 OR 分開;
database_event_list:一個或多個數據庫事件,事件間用 OR 分開;
系統事件觸發器既能夠創建在一個模式上,又能夠創建在整個數據庫上。當創建在模式(SCHEMA)之上時,只有模式所指定用戶的DDL操做和它們所致使的錯誤才激活觸發器, 默認時爲當前用戶模式。當創建在數據庫(DATABASE)之上時,該數據庫全部用戶的DDL操做和他們所致使的錯誤,以及數據庫的啓動和關閉都可激活觸發器。要在數據庫之上創建觸發器時,要求用戶具備ADMINISTER DATABASE TRIGGER權限。
下面給出系統觸發器的種類和事件出現的時機(前或後):
事件 |
容許的時機 |
說明 |
STARTUP |
AFTER |
啓動數據庫實例以後觸發 |
SHUTDOWN |
BEFORE |
關閉數據庫實例以前觸發(非正常關閉不觸發) |
SERVERERROR |
AFTER |
數據庫服務器發生錯誤以後觸發 |
LOGON |
AFTER |
成功登陸鏈接到數據庫後觸發 |
LOGOFF |
BEFORE |
開始斷開數據庫鏈接以前觸發 |
CREATE |
BEFORE,AFTER |
在執行CREATE語句建立數據庫對象以前、以後觸發 |
DROP |
BEFORE,AFTER |
在執行DROP語句刪除數據庫對象以前、以後觸發 |
ALTER |
BEFORE,AFTER |
在執行ALTER語句更新數據庫對象以前、以後觸發 |
DDL |
BEFORE,AFTER |
在執行大多數DDL語句以前、以後觸發 |
GRANT |
BEFORE,AFTER |
執行GRANT語句授予權限以前、以後觸發 |
REVOKE |
BEFORE,AFTER |
執行REVOKE語句收權限以前、以後觸犯發 |
RENAME |
BEFORE,AFTER |
執行RENAME語句更改數據庫對象名稱以前、以後觸犯發 |
AUDIT / NOAUDIT |
BEFORE,AFTER |
執行AUDIT或NOAUDIT進行審計或中止審計以前、以後觸發 |
8.2.4 系統觸發器事件屬性
事件屬性\事件 |
Startup/Shutdown |
Servererror |
Logon/Logoff |
DDL |
DML |
事件名稱 |
* |
* |
* |
* |
* |
數據庫名稱 |
* |
|
|
|
|
數據庫實例號 |
* |
|
|
|
|
錯誤號 |
|
* |
|
|
|
用戶名 |
|
|
* |
* |
|
模式對象類型 |
|
|
|
* |
* |
模式對象名稱 |
|
|
|
* |
* |
列 |
|
|
|
|
* |
除DML語句的列屬性外,其他事件屬性值可經過調用ORACLE定義的事件屬性函數來讀取。
函數名稱 |
數據類型 |
說 明 |
Ora_sysevent |
VARCHAR2(20) |
激活觸發器的事件名稱 |
Instance_num |
NUMBER |
數據庫實例名 |
Ora_database_name |
VARCHAR2(50) |
數據庫名稱 |
Server_error(posi) |
NUMBER |
錯誤信息棧中posi指定位置中的錯誤號 |
Is_servererror(err_number) |
BOOLEAN |
檢查err_number指定的錯誤號是否在錯誤信息棧中,若是在則返回TRUE,不然返回FALSE。在觸發器內調用此函數能夠判斷是否發生指定的錯誤。 |
Login_user |
VARCHAR2(30) |
登錄或註銷的用戶名稱 |
Dictionary_obj_type |
VARCHAR2(20) |
DDL語句所操做的數據庫對象類型 |
Dictionary_obj_name |
VARCHAR2(30) |
DDL語句所操做的數據庫對象名稱 |
Dictionary_obj_owner |
VARCHAR2(30) |
DDL語句所操做的數據庫對象全部者名稱 |
Des_encrypted_password |
VARCHAR2(2) |
正在建立或修改的通過DES算法加密的用戶口令 |
例1:建立觸發器,存放有關事件信息。
DESC ora_sysevent
DESC ora_login_user
-- 建立用於記錄事件用的表
CREATE TABLE ddl_event
(crt_date timestamp PRIMARY KEY,
event_name VARCHAR2( 20),
user_name VARCHAR2( 10),
obj_type VARCHAR2( 20),
obj_name VARCHAR2( 20));
-- 建立觸犯發器
CREATE OR REPLACE TRIGGER tr_ddl
AFTER DDL ON SCHEMA
BEGIN
INSERT INTO ddl_event VALUES
(systimestamp,ora_sysevent, ora_login_user,
ora_dict_obj_type, ora_dict_obj_name);
END tr_ddl;
例2:建立登陸、退出觸發器。
CREATE TABLE log_event
( user_name VARCHAR2( 10),
address VARCHAR2( 20),
logon_date timestamp,
logoff_date timestamp);
-- 建立登陸觸發器
CREATE OR REPLACE TRIGGER tr_logon
AFTER LOGON ON DATABASE
BEGIN
INSERT INTO log_event ( user_name, address, logon_date)
VALUES (ora_login_user, ora_client_ip_address, systimestamp);
END tr_logon;
-- 建立退出觸發器
CREATE OR REPLACE TRIGGER tr_logoff
BEFORE LOGOFF ON DATABASE
BEGIN
INSERT INTO log_event ( user_name, address, logoff_date)
VALUES (ora_login_user, ora_client_ip_address, systimestamp);
END tr_logoff;
8.2.5 使用觸發器謂詞
ORACLE 提供三個參數INSERTING, UPDATING, DELETING 用於判斷觸發了哪些操做。
謂詞 |
行爲 |
INSERTING |
若是觸發語句是 INSERT 語句,則爲TRUE,不然爲FALSE |
UPDATING |
若是觸發語句是 UPDATE語句,則爲TRUE,不然爲FALSE |
DELETING |
若是觸發語句是 DELETE 語句,則爲TRUE,不然爲FALSE |
8.2.6 從新編譯觸發器
若是在觸發器內調用其它函數或過程,當這些函數或過程被刪除或修改後,觸發器的狀態將被標識爲無效。當DML語句激活一個無效觸發器時,ORACLE將從新編譯觸發器代碼,若是編譯時發現錯誤,這將致使DML語句執行失敗。
在PL/SQL程序中能夠調用ALTER TRIGGER語句從新編譯已經建立的觸發器,格式爲:
ALTER TRIGGER [ schema. ] trigger_name COMPILE [ DEBUG ]
其中:DEBUG 選項要器編譯器生成PL/SQL 程序條使其所使用的調試代碼。
8.3 刪除和使能觸發器
l 刪除觸發器:
DROP TRIGGER trigger_name;
當刪除其餘用戶模式中的觸發器名稱,須要具備DROP ANY TRIGGER系統權限,當刪除創建在數據庫上的觸發器時,用戶須要具備ADMINISTER DATABASE TRIGGER系統權限。
此外,當刪除表或視圖時,創建在這些對象上的觸發器也隨之刪除。
l 禁用或啓用觸發器
數據庫TRIGGER 的狀態:
有效狀態(ENABLE):當觸發事件發生時,處於有效狀態的數據庫觸發器TRIGGER 將被觸發。
無效狀態(DISABLE):當觸發事件發生時,處於無效狀態的數據庫觸發器TRIGGER 將不會被觸發,此時就跟沒有這個數據庫觸發器(TRIGGER) 同樣。
數據庫TRIGGER的這兩種狀態能夠互相轉換。格式爲:
ALTER TIGGER trigger_name [ DISABLE | ENABLE ];
-- 例:ALTER TRIGGER emp_view_delete DISABLE;
ALTER TRIGGER語句一次只能改變一個觸發器的狀態,而ALTER TABLE語句則一次可以改變與指定表相關的全部觸發器的使用狀態。格式爲:
ALTER TABLE [ schema. ]table_name {ENABLE |DISABLE} ALL TRIGGERS;
-- 例:使表EMP 上的全部TRIGGER 失效:
ALTER TABLE emp DISABLE ALL TRIGGERS;
8.4 觸發器和數據字典
相關數據字典:USER_TRIGGERS、ALL_TRIGGERS、DBA_TRIGGERS
SELECT TRIGGER_NAME, TRIGGER_TYPE, TRIGGERING_EVENT,
TABLE_OWNER, BASE_OBJECT_TYPE, REFERENCING_NAMES,
STATUS, ACTION_TYPE
FROM user_triggers;
8.5 數據庫觸發器的應用舉例
例1:建立一個DML語句級觸發器,當對emp表執行INSERT, UPDATE, DELETE 操做時,它自動更新dept_summary 表中的數據。因爲在PL/SQL塊中不能直接調用DDL語句,因此,利用ORACLE內置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過程,由它執行DDL語句建立觸發器。
CREATE TABLE dept_summary(
Deptno NUMBER( 2),
Sal_sum NUMBER( 9, 2),
Emp_count NUMBER);
INSERT INTO dept_summary(deptno, sal_sum, emp_count)
SELECT deptno, SUM(sal), COUNT( *)
FROM emp
GROUP BY deptno;
-- 建立一個PL/SQL過程disp_dept_summary
-- 在觸發器中調用該過程顯示dept_summary標中的數據。
CREATE OR REPLACE PROCEDURE disp_dept_summary
IS
Rec dept_summary %ROWTYPE;
CURSOR c1 IS SELECT * FROM dept_summary;
BEGIN
OPEN c1;
FETCH c1 INTO REC;
DBMS_OUTPUT.PUT_LINE( ' deptno sal_sum emp_count ');
DBMS_OUTPUT.PUT_LINE( ' ------------------------------------- ');
WHILE c1 %FOUND LOOP
DBMS_OUTPUT.PUT_LINE(RPAD(rec.deptno, 6) ||
To_char(rec.sal_sum, ' $999,999.99 ') ||
LPAD(rec.emp_count, 13));
FETCH c1 INTO rec;
END LOOP;
CLOSE c1;
END;
BEGIN
DBMS_OUTPUT.PUT_LINE( ' 插入前 ');
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT( '
CREATE OR REPLACE TRIGGER trig1
AFTER INSERT OR DELETE OR UPDATE OF sal ON emp
BEGIN
DBMS_OUTPUT.PUT_LINE( '' 正在執行trig1 觸發器… '' );
DELETE FROM dept_summary;
INSERT INTO dept_summary(deptno, sal_sum, emp_count)
SELECT deptno, SUM(sal), COUNT(*)
FROM emp GROUP BY deptno;
END;
');
INSERT INTO dept(deptno, dname, loc)
VALUES( 90, ‘demo_dept’, ‘none_loc’);
INSERT INTO emp(ename, deptno, empno, sal)
VALUES( USER, 90, 9999, 3000);
DBMS_OUTPUT.PUT_LINE( ' 插入後 ');
Disp_dept_summary();
UPDATE emp SET sal = 1000 WHERE empno = 9999;
DBMS_OUTPUT.PUT_LINE( ' 修改後 ');
Disp_dept_summary();
DELETE FROM emp WHERE empno = 9999;
DELETE FROM dept WHERE deptno = 90;
DBMS_OUTPUT.PUT_LINE( ' 刪除後 ');
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT(‘ DROP TRIGGER trig1’);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END;
例2:建立DML語句行級觸發器。當對emp表執行INSERT, UPDATE, DELETE 操做時,它自動更新dept_summary 表中的數據。因爲在PL/SQL塊中不能直接調用DDL語句,因此,利用ORACLE內置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過程,由它執行DDL語句建立觸發器。
BEGIN
DBMS_OUTPUT.PUT_LINE( ' 插入前 ');
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT(
' CREATE OR REPLACE TRIGGER trig2_update
AFTER UPDATE OF sal ON emp
REFERENCING OLD AS old_emp NEW AS new_emp
FOR EACH ROW
WHEN (old_emp.sal != new_emp.sal)
BEGIN
DBMS_OUTPUT.PUT_LINE( '' 正在執行trig2_update 觸發器… '' );
DBMS_OUTPUT.PUT_LINE( '' sal 舊值: '' || :old_emp.sal);
DBMS_OUTPUT.PUT_LINE( '' sal 新值: '' || :new_emp.sal);
UPDATE dept_summary
SET sal_sum=sal_sum + :new_emp.sal - :old_emp.sal
WHERE deptno = :new_emp.deptno;
END; '
);
DBMS_UTILITY.EXEC_DDL_STATEMENT(
' CREATE OR REPLACE TRIGGER trig2_insert
AFTER INSERT ON emp
REFERENCING NEW AS new_emp
FOR EACH ROW
DECLARE
I NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE( '' 正在執行trig2_insert 觸發器… '' );
SELECT COUNT(*) INTO I
FROM dept_summary WHERE deptno = :new_emp.deptno;
IF I > 0 THEN
UPDATE dept_summary
SET sal_sum=sal_sum+:new_emp.sal,
Emp_count=emp_count+1
WHERE deptno = :new_emp.deptno;
ELSE
INSERT INTO dept_summary
VALUES (:new_emp.deptno, :new_emp.sal, 1);
END IF;
END; '
);
DBMS_UTILITY.EXEC_DDL_STATEMENT(
' CREATE OR REPLACE TRIGGER trig2_delete
AFTER DELETE ON emp
REFERENCING OLD AS old_emp
FOR EACH ROW
DECLARE
I NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE( '' 正在執行trig2_delete 觸發器… '' );
SELECT emp_count INTO I
FROM dept_summary WHERE deptno = :old_emp.deptno;
IF I >1 THEN
UPDATE dept_summary
SET sal_sum=sal_sum - :old_emp.sal,
Emp_count=emp_count - 1
WHERE deptno = :old_emp.deptno;
ELSE
DELETE FROM dept_summary WHERE deptno = :old_emp.deptno;
END IF;
END; '
);
INSERT INTO dept(deptno, dname, loc)
VALUES( 90, ' demo_dept ', ' none_loc ');
INSERT INTO emp(ename, deptno, empno, sal)
VALUES( USER, 90, 9999, 3000);
INSERT INTO emp(ename, deptno, empno, sal)
VALUES( USER, 90, 9998, 2000);
DBMS_OUTPUT.PUT_LINE( ' 插入後 ');
Disp_dept_summary();
UPDATE emp SET sal = sal * 1.1 WHERE deptno = 90;
DBMS_OUTPUT.PUT_LINE( ' 修改後 ');
Disp_dept_summary();
DELETE FROM emp WHERE deptno = 90;
DELETE FROM dept WHERE deptno = 90;
DBMS_OUTPUT.PUT_LINE( ' 刪除後 ');
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT( ' DROP TRIGGER trig2_update ');
DBMS_UTILITY.EXEC_DDL_STATEMENT( ' DROP TRIGGER trig2_insert ');
DBMS_UTILITY.EXEC_DDL_STATEMENT( ' DROP TRIGGER trig2_delete ');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END;
例3:利用ORACLE提供的條件謂詞INSERTING、UPDATING和DELETING建立與例2具備相同功能的觸發器。
BEGIN
DBMS_OUTPUT.PUT_LINE( ' 插入前 ');
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT(
' CREATE OR REPLACE TRIGGER trig2
AFTER INSERT OR DELETE OR UPDATE OF sal
ON emp
REFERENCING OLD AS old_emp NEW AS new_emp
FOR EACH ROW
DECLARE
I NUMBER;
BEGIN
IF UPDATING AND :old_emp.sal != :new_emp.sal THEN
DBMS_OUTPUT.PUT_LINE( '' 正在執行trig2 觸發器… '' );
DBMS_OUTPUT.PUT_LINE( '' sal 舊值: '' || :old_emp.sal);
DBMS_OUTPUT.PUT_LINE( '' sal 新值: '' || :new_emp.sal);
UPDATE dept_summary
SET sal_sum=sal_sum + :new_emp.sal - :old_emp.sal
WHERE deptno = :new_emp.deptno;
ELSIF INSERTING THEN
DBMS_OUTPUT.PUT_LINE( '' 正在執行trig2觸發器… '' );
SELECT COUNT(*) INTO I
FROM dept_summary
WHERE deptno = :new_emp.deptno;
IF I > 0 THEN
UPDATE dept_summary
SET sal_sum=sal_sum+:new_emp.sal,
Emp_count=emp_count+1
WHERE deptno = :new_emp.deptno;
ELSE
INSERT INTO dept_summary
VALUES (:new_emp.deptno, :new_emp.sal, 1);
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE( '' 正在執行trig2觸發器… '' );
SELECT emp_count INTO I
FROM dept_summary WHERE deptno = :old_emp.deptno;
IF I > 1 THEN
UPDATE dept_summary
SET sal_sum=sal_sum - :old_emp.sal,
Emp_count=emp_count - 1
WHERE deptno = :old_emp.deptno;
ELSE
DELETE FROM dept_summary
WHERE deptno = :old_emp.deptno;
END IF;
END IF;
END; '
);
INSERT INTO dept(deptno, dname, loc)
VALUES( 90, ' demo_dept ', ' none_loc ');
INSERT INTO emp(ename, deptno, empno, sal)
VALUES( USER, 90, 9999, 3000);
INSERT INTO emp(ename, deptno, empno, sal)
VALUES( USER, 90, 9998, 2000);
DBMS_OUTPUT.PUT_LINE( ' 插入後 ');
Disp_dept_summary();
UPDATE emp SET sal = sal * 1.1 WHERE deptno = 90;
DBMS_OUTPUT.PUT_LINE( ' 修改後 ');
Disp_dept_summary();
DELETE FROM emp WHERE deptno = 90;
DELETE FROM dept WHERE deptno = 90;
DBMS_OUTPUT.PUT_LINE( ' 刪除後 ');
Disp_dept_summary();
DBMS_UTILITY.EXEC_DDL_STATEMENT( ' DROP TRIGGER trig2 ');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || ' --- ' ||SQLERRM);
END;
例4:建立INSTEAD OF 觸發器。首先建立一個視圖myview, 因爲該視圖是複合查詢所產生的視圖,因此不能執行DML語句。根據用戶對視圖所插入的數據判斷須要將數據插入到哪一個視圖基表中,而後對該基表執行插入操做。
DECLARE
No NUMBER;
Name VARCHAR2( 20);
BEGIN
DBMS_UTILITY.EXEC_DDL_STATEMENT( '
CREATE OR REPLACE VIEW myview AS
SELECT empno, ename, '' E '' type FROM emp
UNION
SELECT dept.deptno, dname, '' D '' FROM dept
');
-- 建立INSTEAD OF 觸發器trigger3;
DBMS_UTILITY.EXEC_DDL_STATEMENT( '
CREATE OR REPLACE TRIGGER trig3
INSTEAD OF INSERT ON myview
REFERENCING NEW n
FOR EACH ROW
DECLARE
Rows INTEGER;
BEGIN
DBMS_OUTPUT.PUT_LINE( '' 正在執行trig3觸發器… '' );
IF :n.type = '' D '' THEN
SELECT COUNT(*) INTO rows
FROM dept WHERE deptno = :n.empno;
IF rows = 0 THEN
DBMS_OUTPUT.PUT_LINE( '' 向dept表中插入數據… '' );
INSERT INTO dept(deptno, dname, loc)
VALUES (:n.empno, :n.ename, '' none’’);
ELSE
DBMS_OUTPUT.PUT_LINE( '' 編號爲 '' || :n.empno||
'' 的部門已存在,插入操做失敗! '' );
END IF;
ELSE
SELECT COUNT(*) INTO rows
FROM emp WHERE empno = :n.empno;
IF rows = 0 THEN
DBMS_OUTPUT.PUT_LINE( '’向emp表中插入數據…’’);
INSERT INTO emp(empno, ename)
VALUES(:n.empno, :n.ename);
ELSE
DBMS_OUTPUT.PUT_LINE( ''編號爲 '' || :n.empno ||
''的人員已存在,插入操做失敗! '');
END IF;
END IF;
END;
' );
INSERT INTO myview VALUES (70, 'demo ' , 'D ' );
INSERT INTO myview VALUES (9999, USER, 'E ' );
SELECT deptno, dname INTO no, name FROM dept WHERE deptno=70;
DBMS_OUTPUT.PUT_LINE( '員工編號: ' ||TO_CHAR(no)|| '姓名: ' ||name);
SELECT empno, ename INTO no, name FROM emp WHERE empno=9999;
DBMS_OUTPUT.PUT_LINE( '部門編號: ' ||TO_CHAR(no)|| '姓名: ' ||name);
DELETE FROM emp WHERE empno=9999;
DELETE FROM dept WHERE deptno=70;
DBMS_UTILITY.EXEC_DDL_STATEMENT( ' DROP TRIGGER trig3 ' );
END;
例5:利用ORACLE事件屬性函數,建立一個系統事件觸發器。首先建立一個事件日誌表eventlog,由它存儲用戶在當前數據庫中所建立的數據庫對象,以及用戶的登錄和註銷、數據庫的啓動和關閉等事件,以後建立trig4_ddl、trig4_before和trig4_after觸發器,它們調用事件屬性函數將各個事件記錄到eventlog數據表中。
BEGIN
-- 建立用於記錄事件日誌的數據表
DBMS_UTILITY.EXEC_DDL_STATEMENT( '
CREATE TABLE eventlog(
Eventname VARCHAR2(20) NOT NULL,
Eventdate date default sysdate,
Inst_num NUMBER NULL,
Db_name VARCHAR2(50) NULL,
Srv_error NUMBER NULL,
Username VARCHAR2(30) NULL,
Obj_type VARCHAR2(20) NULL,
Obj_name VARCHAR2(30) NULL,
Obj_owner VARCHAR2(30) NULL
)
');
-- 建立DDL觸發器trig4_ddl
DBMS_UTILITY.EXEC_DDL_STATEMENT( '
CREATE OR REPLACE TRIGGER trig4_ddl
AFTER CREATE OR ALTER OR DROP
ON DATABASE
DECLARE
Event VARCHAR2(20);
Typ VARCHAR2(20);
Name VARCHAR2(30);
Owner VARCHAR2(30);
BEGIN
-- 讀取DDL事件屬性
Event := SYSEVENT;
Typ := DICTIONARY_OBJ_TYPE;
Name := DICTIONARY_OBJ_NAME;
Owner := DICTIONARY_OBJ_OWNER;
--將事件屬性插入到事件日誌表中
INSERT INTO scott.eventlog(eventname, obj_type, obj_name, obj_owner)
VALUES(event, typ, name, owner);
END;
');
-- 建立LOGON、STARTUP和SERVERERROR 事件觸發器
DBMS_UTILITY.EXEC_DDL_STATEMENT( '
CREATE OR REPLACE TRIGGER trig4_after
AFTER LOGON OR STARTUP OR SERVERERROR
ON DATABASE
DECLARE
Event VARCHAR2(20);
Instance NUMBER;
Err_num NUMBER;
Dbname VARCHAR2(50);
User VARCHAR2(30);
BEGIN
Event := SYSEVENT;
IF event = '' LOGON '' THEN
User := LOGIN_USER;
INSERT INTO eventlog(eventname, username)
VALUES(event, user);
ELSIF event = '' SERVERERROR '' THEN
Err_num := SERVER_ERROR(1);
INSERT INTO eventlog(eventname, srv_error)
VALUES(event, err_num);
ELSE
Instance := INSTANCE_NUM;
Dbname := DATABASE_NAME;
INSERT INTO eventlog(eventname, inst_num, db_name)
VALUES(event, instance, dbname);
END IF;
END;
');
-- 建立LOGOFF和SHUTDOWN 事件觸發器
DBMS_UTILITY.EXEC_DDL_STATEMENT( '
CREATE OR REPLACE TRIGGER trig4_before
BEFORE LOGOFF OR SHUTDOWN
ON DATABASE
DECLARE
Event VARCHAR2(20);
Instance NUMBER;
Dbname VARCHAR2(50);
User VARCHAR2(30);
BEGIN
Event := SYSEVENT;
IF event = '' LOGOFF '' THEN
User := LOGIN_USER;
INSERT INTO eventlog(eventname, username)
VALUES(event, user);
ELSE
Instance := INSTANCE_NUM;
Dbname := DATABASE_NAME;
INSERT INTO eventlog(eventname, inst_num, db_name)
VALUES(event, instance, dbname);
END IF;
END;
');
END;
CREATE TABLE mydata(mydate NUMBER);
CONNECT SCOTT /TIGER
COL eventname FORMAT A10
COL eventdate FORMAT A12
COL username FORMAT A10
COL obj_type FORMAT A15
COL obj_name FORMAT A15
COL obj_owner FORMAT A10
SELECT eventname, eventdate, obj_type, obj_name, obj_owner, username, Srv_error
FROM eventlog;
DROP TRIGGER trig4_ddl;
DROP TRIGGER trig4_before;
DROP TRIGGER trig4_after;
DROP TABLE eventlog;
DROP TABLE mydata;
8.6 數據庫觸發器的應用實例
用戶可使用數據庫觸發器實現各類功能:
l 複雜的審計功能;
例:將EMP 表的變化狀況記錄到AUDIT_TABLE和AUDIT_TABLE_VALUES中。
CREATE TABLE audit_table(
Audit_id NUMBER,
User_name VARCHAR2( 20),
Now_time DATE,
Terminal_name VARCHAR2( 10),
Table_name VARCHAR2( 10),
Action_name VARCHAR2( 10),
Emp_id NUMBER( 4));
CREATE TABLE audit_table_val(
Audit_id NUMBER,
Column_name VARCHAR2( 10),
Old_val NUMBER( 7, 2),
New_val NUMBER( 7, 2));
CREATE SEQUENCE audit_seq
START WITH 1000
INCREMENT BY 1
NOMAXVALUE
NOCYCLE NOCACHE;
CREATE OR REPLACE TRIGGER audit_emp
AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW
DECLARE
Time_now DATE;
Terminal CHAR( 10);
BEGIN
Time_now: =sysdate;
Terminal: =USERENV( ' TERMINAL ');
IF INSERTING THEN
INSERT INTO audit_table
VALUES(audit_seq.NEXTVAL, user, time_now,
terminal, ' EMP ', ' INSERT ', :new.empno);
ELSIF DELETING THEN
INSERT INTO audit_table
VALUES(audit_seq.NEXTVAL, user, time_now,
terminal, ' EMP ', ' DELETE ', :old.empno);
ELSE
INSERT INTO audit_table
VALUES(audit_seq.NEXTVAL, user, time_now,
terminal, ' EMP ', ' UPDATE ', :old.empno);
IF UPDATING( ' SAL ') THEN
INSERT INTO audit_table_val
VALUES(audit_seq.CURRVAL, ' SAL ', :old.sal, :new.sal);
ELSE UPDATING( ' DEPTNO ')
INSERT INTO audit_table_val
VALUES(audit_seq.CURRVAL, ' DEPTNO ', :old.deptno, :new.deptno);
END IF;
END IF;
END;
l 加強數據的完整性管理;
例:修改DEPT表的DEPTNO列時,同時把EMP表中相應的DEPTNO也做相應的修改;
CREATE SEQUENCE update_sequence
INCREMENT BY 1
START WITH 1000
MAXVALUE 5000 CYCLE;
ALTER TABLE emp
ADD update_id NUMBER;
CREATE OR REPLACE PACKAGE integritypackage AS
Updateseq NUMBER;
END integritypackage;
CREATE OR REPLACE PACKAGE BODY integritypackage AS
END integritypackage;
CREATE OR REPLACE TRIGGER dept_cascade1
BEFORE UPDATE OF deptno ON dept
DECLARE
Dummy NUMBER;
BEGIN
SELECT update_sequence.NEXTVAL INTO dummy FROM dual;
Integritypackage.updateseq: = dummy;
END;
CREATE OR REPLACE TRIGGER dept_cascade2
AFTER DELETE OR UPDATE OF deptno ON dept
FOR EACH ROW
BEGIN
IF UPDATING THEN
UPDATE emp SET deptno =:new.deptno,
update_id =integritypackage.updateseq
WHERE emp.deptno =:old.deptno AND update_id IS NULL;
END IF;
IF DELETING THEN
DELETE FROM emp
WHERE emp.deptno =:old.deptno;
END IF;
END;
CREATE OR REPLACE TRIGGER dept_cascade3
AFTER UPDATE OF deptno ON dept
BEGIN
UPDATE emp SET update_id = NULL
WHERE update_id =integritypackage.updateseq;
END;
SELECT * FROM EMP ORDER BY DEPTNO;
UPDATE dept SET deptno = 25 WHERE deptno = 20;
l 幫助實現安全控制;
例:保證對EMP表的修改僅在工做日的工做時間;
CREATE TABLE company_holidays( day DATE);
INSERT INTO company_holidays
VALUES(sysdate);
INSERT INTO company_holidays
VALUES(TO_DATE( ' 21-10月-01 ', ' DD-MON-YY '));
CREATE OR REPLACE TRIGGER emp_permit_change
BEFORE INSERT OR DELETE OR UPDATE ON emp
DECLARE
Dummy NUMBER;
Not_on_weekends EXCEPTION;
Not_on_holidays EXCEPTION;
Not_working_hours EXCEPTION;
BEGIN
/* check for weekends */
IF TO_CHAR(SYSDATE, ' DAY ') IN ( ' 星期六 ', ' 星期日 ') THEN
RAISE not_on_weekends;
END IF;
/* check for company holidays */
SELECT COUNT( *) INTO dummy FROM company_holidays
WHERE TRUNC( day) =TRUNC(SYSDATE);
IF dummy > 0 THEN
RAISE not_on_holidays;
END IF;
/* check for work hours(8:00 AM to 18:00 PM */
IF (TO_CHAR(SYSDATE, ' HH24 ') < 8 OR TO_CHAR(SYSDATE, ' HH24 ') > 18) THEN
RAISE not_working_hours;
END IF;
EXCEPTION
WHEN not_on_weekends THEN
RAISE_APPLICATION_ERROR( - 20324,
' May not change employee table during the weekends ');
WHEN not_on_holidays THEN
RAISE_APPLICATION_ERROR( - 20325,
' May not change employee table during a holiday ');
WHEN not_working_hours THEN
RAISE_APPLICATION_ERROR( - <