PL/SQL 程序設計簡介

PL/SQL 程序設計簡介程序員

SQL語言只是訪問、操做數據庫的語言,並非一種具備流程控制的程序設計語言,而只有程序設計語言才能用於應用軟件的開發。PL /SQL是一種高級數據庫程序設計語言,該語言專門用於在各類環境下對ORACLE數據庫進行訪問。因爲該語言集成於數據庫服務器中,因此PL/SQL代碼能夠對數據進行快速高效的處理。除此以外,能夠在ORACLE數據庫的某些客戶端工具中,使用PL/SQL語言也是該語言的一個特色。算法

1.1   SQLPL/SQLsql

1.1.1  什麼是PL/SQL?數據庫

PL/SQL是 Procedure Language & Structured Query Language 的縮寫。ORACLESQL是支持ANSI(American national Standards Institute)ISO92 (International Standards Organization)標準的產品。PL/SQL是對SQL語言存儲過程語言的擴展。從ORACLE6之後,ORACLERDBMS附帶了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/SQLORACLE系統的核心語言,如今ORACLE的許多部件都是由PL/SQL寫成。PL/SQL中可使用的SQL語句有:

INSERTUPDATEDELETESELECT INTOCOMMITROLLBACKSAVEPOINT

提示:在 PL/SQL中只能用 SQL語句中的 DML 部分,不能用 DDL 部分,若是要在PL/SQL中使用DDL(CREATE  table  )的話,只能以動態的方式來使用。

ORACLE 的 PL/SQL 組件在對 PL/SQL 程序進行解釋時,同時對在其所使用的表名、列名及數據類型進行檢查。

PL/SQL 能夠在SQL*PLUS 中使用。

PL/SQL 能夠在高級語言中使用。

PL/SQL能夠在ORACLE的開發工具中使用(如:SQL DeveloperProcedure Builder)。

其它開發工具也能夠調用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

 

日期型

公元前471211日至公元后47121231

 

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 字句還可將列表達式、ROWIDREF值返回到輸出變量中。在使用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 子句能夠檢索被修改行的ROWIDREF值,以及行中被修改列的列表達式,並可將他們存儲到PL/SQL變量或複合變量中;當UPDATE語句修改多行數據時,RETURNING 子句能夠將被修改行的ROWIDREF值,以及列表達式值返回到複合變量數組中。在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 子句能夠檢索被刪除行的ROWIDREF值,以及被刪除列的列表達式,並可將他們存儲到PL/SQL變量或複合變量中;當DELETE語句刪除多行數據時,RETURNING 子句能夠將被刪除行的ROWIDREF值,以及列表達式值返回到複合變量數組中。在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( 30NOT  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_nameVARRAY數據類型的名稱,size是下整數,表示可容納的成員的最大數量,每一個成員的數據類型是element_type。默認成員能夠取空值,不然須要使用NOT NULL加以限制。對於VARRAY數據類型來講,必須通過三個步驟,分別是:定義、聲明、初始化。

 

6


 


DECLARE 

-- 定義一個最多保存5個VARCHAR(25)數據類型成員的VARRAY數據類型 

   TYPE reg_varray_type  IS VARRAY( 5OF  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,  0INTO :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) :刪除從nm的成員

EXTEND

EXTEND:添加一個null成員

EXTEND(n):添加nnull成員

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( intFROM 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, 0INTO 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的流程控制語句包括以下三類:

控制語句: IF 語句

循環語句: LOOP語句, EXIT語句

順序語句: 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( 12OF  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/SQLGOTO語句是無條件跳轉到指定的標號去的意思。語法以下:

 

GOTO label; 

...... 

<<label >>  /* 標號是用<< >>括起來的標識符  */

 

 

注意,在如下地方使用是不合法的,編譯時會出錯誤。

跳轉到非執行語句前面。

跳轉到子塊中。

跳轉到循環語句中。

跳轉到條件語句中。

從異常處理部分跳轉到執行。

從條件語句的一部分跳轉到另外一部分。

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塊中執行SELECTINSERTDELETEUPDATE語句時,ORACLE會在內存中爲其分配上下文區(Context Area),即緩衝區。遊標是指向該區的一個指針,或是命名一個工做區(Work Area),或是一種結構化數據類型。它爲應用等量齊觀提供了一種對具備多行數據查詢結果集中的每一行數據分別進行單獨處理的方法,是設計嵌入式SQL語句的應用程序的經常使用編程方式。

 在每一個用戶會話中,能夠同時打開多個遊標,其數量由數據庫初始化參數文件中的OPEN_CURSORS參數定義。

對於不一樣的SQL語句,遊標的使用狀況不一樣:

SQL語句

遊標

非查詢語句

隱式的

結果是單行的查詢語句

隱式的或顯示的

結果是多行的查詢語句

顯示的

 

4.1.1 處理顯式遊標

 

1. 顯式遊標處理

顯式遊標處理需四個 PL/SQL步驟:

定義/聲明遊標:就是定義一個遊標名,以及與其相對應的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」的數據。

 

打開遊標:就是執行遊標所對應的SELECT 語句,將其查詢結果放入工做區,而且指針指向工做區的首部,標識遊標結果集合。若是遊標查詢語句中帶有FOR UPDATE選項,OPEN 語句還將鎖定數據庫表中游標結果集合對應的數據行。

格式:

 

OPEN cursor_name [ ([parameter => ] value [ , [parameter => ] value]…)];

 

在向遊標傳遞參數時,可使用與函數參數相同的傳值方法,即位置表示法和名稱表示法。PL/SQL 程序不能用OPEN 語句重複打開一個遊標。

 

提取遊標數據:就是檢索結果集合中的數據行,放入指定的輸出變量中。 

格式:

 

FETCH cursor_name  INTO {variable_list  | record_variable };

 

 

執行FETCH語句時,每次返回一個數據行,而後自動將遊標移動指向下一個數據行。當檢索到最後一行數據時,若是再次執行FETCH語句,將操做失敗,並將遊標屬性%NOTFOUND置爲TRUE。因此每次執行完FETCH語句後,檢查遊標屬性%NOTFOUND就能夠判斷FETCH語句是否執行成功並返回一個數據行,以便肯定是否給對應的變量賦了值。

對該記錄進行處理;

繼續處理,直到活動集合中沒有記錄;

關閉遊標:當提取和處理完遊標結果集合數據後,應及時關閉遊標,以釋放該遊標所佔用的系統資源,並使該遊標的工做區變成無效,不能再使用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  10IS 

     SELECT DEPARTMENT_NAME, LOCATION_ID  FROM DEPARTMENTS  

     WHERE DEPARTMENT_ID  <= dept_no; 

   CURSOR c3(dept_no  NUMBER  DEFAULT  10IS  

     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循環語句,自動執行遊標的OPENFETCHCLOSE語句和循環語句的功能;當進入循環時,遊標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;


 

10PL/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

當UPDATEDELETE 語句的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 聲明遊標,則可在DELETEUPDATE 語句中使用

WHERE CURRENT OF cursor_name子句,修改或刪除遊標結果集合當前行對應的數據庫表中的數據行。

 

例13EMPLOYEES表中查詢某部門的員工狀況,將其工資最低定爲 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 InterfacePro*c 程序等)中聲明的遊標變量。

OPENFOR 語句能夠在關閉當前的遊標變量以前從新打開遊標變量,而不會致使CURSOR_ALREAD_OPEN異常錯誤。新打開遊標變量時,前一個查詢的內存處理區將被釋放。

 

2. 提取遊標變量數據

使用FETCH語句提取遊標變量結果集合中的數據。格式爲:

 

 

FETCH {cursor_variable_name  | :host_cursor_variable_name} 

INTO {variable  [ , variable ]| record_variable};

 

其中:cursor_variable_namehost_cursor_variable_name分別爲遊標變量和宿主遊標變量名稱;variablerecord_variable分別爲普通變量和記錄變量名稱。

 

3. 關閉遊標變量

CLOSE語句關閉遊標變量,格式爲:

 

 

CLOSE {cursor_variable_name  | :host_cursor_variable_name}

 

其中:cursor_variable_namehost_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 '11)); 

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  IN12); 

   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 在執行部分引起異常錯誤

    當一個異常錯誤在執行部分引起時,有下列狀況:

若是當前塊對該異常錯誤設置了處理,則執行它併成功完成該塊的執行,而後控制轉給包含塊。

若是沒有對當前塊異常錯誤設置定義處理器,則經過在包含塊中引起它來傳播異常錯誤。而後對該包含塊執行步驟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( 82); 

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( 82); 

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、共同使用的代碼能夠只須要被編寫和測試一次,而被須要該代碼的任何應用程序(如:.NETC++JAVAVB程序,也能夠是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       在包體中實現的過程、函數、遊標的名稱必須與包說明中的過程、函數、遊標一致,包括名稱、參數的名稱以及參數的模式(INOUTIN OUT)。並建設按包說明中的次序定義包體中具體的實現。

u       在包體中聲明的數據類型、變量、常量都是私有的,只能在包體中使用而不能被印刷體外的應用程序訪問與使用。

u       在包體執行部分,可對包說明,包體中聲明的公有或私有變量進行初始化或其它設置。

 

建立程序包說明語法格式:

 

CREATE  [ OR REPLACE ] PACKAGE package_name 

   [ AUTHID {CURRENT_USER | DEFINER} ] 

  { IS  |  AS

   [ 公有數據類型定義[公有數據類型定義 ]…] 

   [ 公有遊標聲明[公有遊標聲明 ]…] 

   [ 公有變量、常量聲明[公有變量、常量聲明 ]…] 

   [ 公有函數聲明[公有函數聲明 ]…] 

   [ 公有過程聲明[公有過程聲明 ]…] 

END  [ package_name ];

 

 

其中:AUTHID CURRENT_USERAUTHID 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_deptremove_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( 72);  -- 當前工資 

   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,  130); 

   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事件指的是對數據庫的表進行的INSERTUPDATEDELETE操做或對視圖進行相似的操做。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語句(如CREATEALTERDROP語句在數據庫中建立、修改、刪除模式對象)、數據庫系統事件(如系統啓動或退出、異常錯誤)、用戶事件(如登陸或退出數據庫)。

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 子句中可使用相關名稱參照當前的新、舊列值,默認的相關名稱分別爲OLDNEW。觸發器的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操做(INSERTUPDATEDELETE)。既能夠是單個觸發事件,也能夠是多個觸發事件的組合(只能使用OR邏輯組合,不能使用AND邏輯組合)。

l         條件謂詞:當在觸發器中包含多個觸發事件(INSERTUPDATEDELETE)的組合時,爲了分別針對不一樣的事件進行不一樣的處理,須要使用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 子句中可使用相關名稱參照當前的新、舊列值,默認的相關名稱分別爲OLDNEW。觸發器的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 ALTERDROP 等。而數據庫系統事件包括數據庫服務器的啓動或關閉,用戶的登陸與退出、數據庫服務錯誤等。建立系統觸發器的語法以下: 

建立觸發器的通常語法是:

 

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

BEFOREAFTER

在執行CREATE語句建立數據庫對象以前、以後觸發

DROP

BEFOREAFTER

在執行DROP語句刪除數據庫對象以前、以後觸發

ALTER

BEFOREAFTER

在執行ALTER語句更新數據庫對象以前、以後觸發

DDL

BEFOREAFTER

在執行大多數DDL語句以前、以後觸發

GRANT

BEFOREAFTER

執行GRANT語句授予權限以前、以後觸發

REVOKE

BEFOREAFTER

執行REVOKE語句收權限以前、以後觸犯發

RENAME

BEFOREAFTER

執行RENAME語句更改數據庫對象名稱以前、以後觸犯發

AUDIT / NOAUDIT

BEFOREAFTER

執行AUDITNOAUDIT進行審計或中止審計以前、以後觸發

 

 

8.2.4 系統觸發器事件屬性

 


事件屬性\事件

Startup/Shutdown

Servererror

Logon/Logoff

DDL

DML

事件名稱

數據庫名稱

 

 

 

 

數據庫實例號

 

 

 

 

錯誤號

 

 

 

 

用戶名

 

 

 

模式對象類型

 

 

 

模式對象名稱

 

 

 

 

 

 

 

 

DML語句的列屬性外,其他事件屬性值可經過調用ORACLE定義的事件屬性函數來讀取。


函數名稱

數據類型

   

Ora_sysevent

VARCHAR220

激活觸發器的事件名稱

Instance_num

NUMBER

數據庫實例名

Ora_database_name

VARCHAR250

數據庫名稱

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_TRIGGERSALL_TRIGGERSDBA_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( 92), 

 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( USER9099993000); 


 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( USER9099993000); 

   INSERT  INTO emp(ename, deptno, empno, sal) 

     VALUES( USER9099982000); 

  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提供的條件謂詞INSERTINGUPDATINGDELETING建立與例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( USER9099993000); 

   INSERT  INTO emp(ename, deptno, empno, sal) 

     VALUES( USER9099982000); 

  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_ddltrig4_beforetrig4_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_TABLEAUDIT_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 ') > 18THEN 

  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( - <

相關文章
相關標籤/搜索