1.存储过程

存储过程是一种命名的PL/SQL块,它既可以没有参数,也可以有若干参数输入,输出参数,甚至可以有多个既作为输入又作为输出的参数,但它通常没有返回值。
存储过程被保存在数据库中,它不可以被SQL语句直接执行或调用,只能通过EXECUT命令执行或在PL/SQL块内部被调用。
由于存储过程是已经编译好的代码,所在被调用或引用时,其执行效率非常高。

1.1 创建存储过程

创建一个存储过程与编写一个普通的PL/SQL块有很多相似的地方,例如,两者都包括声明部分,执行部分和异常处理3部分。但这二者之间的实现细节还是有很多差别的,例如创建存储过程需要使用PROCEDURE关键字,在关键字后面就是过程名和参数列表;创建存储过程不需要使用DECLARE关键字,而是使用CREATE或REPLACE关键字,其基本语法格式如下:

CREATE [OR REPLACE] PROCEDURE pro_name [(parameter1[,parameter2]...)] IS|AS
BEGINplsql_sentences;
[EXCEPTION][dowith_sentences;]
END [pro_name];pro_name:存储过程的名称,如果数据库中已经存在了此名称,则可以指定OR REPLACE关键字,这样新的存储过程将覆盖原来的存储过程。parameterr1:存储过程的参数。若是输入参数,则需要在其后指定IN关键字;若是输出参数,则需要在其后面指定OUT关键字。在IN或OUT关键字的后面是参数的数据类型,但不能指定该类型的长度。plsql_sentence: PL/SQL语句,它是存储过程功能实现的主体。dowith_sentences: 异常处理语句,也是PL/SQL语句,这是一个可选项。

注意:上面的语法中的parameter1是存储过程被调用/执行时用到的参数,而不是存储过程内定义的内部变量,内部变量要在IS|AS关键字后面定义,并使用分号(;)结束。

(1)创建一个存储过程,该存储过程实现向dept表中插入一条记录,代码如下:

SQL> create procedure pro_insertDept is begininsert into dept values(77,'市场扩展部','JILIN');commit;dbms_output.put_line('插入记录成功!');end pro_insertDept;/

(2)在当前模式下,如果数据库中存在同名的存储过程,则要求新创建的存储过程覆盖掉已存在的存储过程;如果不存在同名的存储过程,则可直接创建,代码如下:

SQL> create or replace procedure pro_insertDept isbegininsert into dept values(99,'市场扩展部','BEIJING');commit;dbms_output.put_line('插入记录成功!');end pro_insertDept;/

(3)在SQL*Plus环境中,使用EXECUTE命令执行pro_insertDept存储过程,具体代码如下:

SQL> execute pro_insertDept;

代码中的EXECUTE命令也可以简写为EXEC。但有时候需要在一个PL/SQL块中调用某个存储过程。

在PL/SQL块中调用存储过程pro_insertDep,然后执行这个PL/SQL块,具体代码如下:

SQL> set serveroutput on
SQL> beginpro_insertDept;end;/在创建存储过程的语法中,IS关键字也可以使用AS关键字来替代,效果是相同的。

1.2 存储过程的参数

前面所创建的存储过程都是简单的存储过程,它们都没有涉及参数。Oracle为了增强存储过程的灵活性,提供向存储过程传入参数的功能。
参数是一种向程序单元输入和输出数据的机制,存储过程可以接受多个参数,参数模式包括IN / OUT 和 IN OUT 3种。

(1)IN模式参数

这是一种输入类型的参数,参数值由调用方传入,并且只能被存储过程读取。这种参数模式是常用的,也是默认的参数模式,关键字IN位于参数名称之后。例如,下面将声明一个带有IN模式的存储过程。
    
    创建一个存储过程,并定义3个IN模式的变量,然后将这3个变量的值插入到dept表中,代码如下:

 SQL> create or replace procedure insert_dept(num_deptno in number,           -- 定义IN模式的变量,它存储部门编号var_ename in varchar2,           -- 定义IN模式的变量,它存储部门名称var_loc in varchar2) isbegininsert into deptvalues(num_deptno,var_ename,var_loc);    -- 向dept表中插入记录commit;                       -- 提交数据库end insert_dept;/

这样通过上面的代码就成功创建了一个存储过程,需要注意的是:参数的类型不能指定长度。在调用或执行这种IN模式的存储过程时,用户需要向存储过程中传递若干参数值,以保证执行部分(即BEGIN部分)有具体的数值参与数据操作。

向存储过程传入参数可以有如下3种方式:

===== 指定名称传递

指定名称传递是指在向存储过程传递参数时需要指定参数名称,即参数名称在左侧,中间是赋值符号"=>",右侧是参数值,其语法格式如下:

pro_name(parameter1=>value1[,parameter2=>value2]...)parameter1: 参数名称。在传递参数值时,这个参数名称与存储过程中定义的参数顺序无关。value1: 参数值。在它的左侧不是常规的赋值符号"=",而是一种新的赋值符号"=>",需要注意参数值的类型要与参数的定义类型兼容。

在PL/SQL块中调用存储过程insert_dept,然后使用"指定名称"的方式向其传入参数值,最后执行当前的PL/SQL块,代码及运行结果如下:

SQL> begininsert_dept(var_ename=>'采购部',var_loc=>'成都',num_deptno=>15);end;/PL/SQL 过程已成功完成。

在创建存储过程时,其参数的定义顺序是num_deptno, var_ename, var_loc;而在执行存储过程时,参数的传递顺序是var_ename, var_loc, num_deptno, 通过对比可以看到,使用"指定名称"的方式传递参数值与参数的定义顺序无关,但与参数个数有关。

===== 按位置传递

指定名称传递参数虽然直观易读,但也有缺点,就是参数过多时,会显得代码冗长,反而变得不容易阅读。这样用户就可以采取按位置传递参数,采用这种方式时,用户提供的参数值顺序必须与存储过程中定义的参数顺序相同。

在PL/SQL块中调用存储过程insert_dept,然后使用"按位置传递"的方式向其传入参数值,最后执行当前的PL/SQL块,代码如下:

SQL> begininsert_dept(28,'工程部','洛阳');end;/PL/SQL 过程已成功完成。注意: 有时候参数过多,用户不容易记住参数的顺序和类型,用户可以通过DESC命令来查看存储过程中的参数定义信息,这些信息包括参数名,参数定义顺序,参数类型和参数模式等。

===== 混合方式传递

混合方式就是将前两种方式结合到一起,这样就可以兼顾二者的优点。

在PL/SQL块中调用存储过程insert_dept,然后使用"按位置传递"方式传入第一个参数值,接下来使用"指定名称传递"方式传入剩余的两个参数值,最后执行当前的PL/SQL块,代码及运行结果如下:

 SQL> exec insert_dept(38,var_loc=>'济南',var_ename=>'测试部');PL/SQL 过程已成功完成。

在上面的代码中,使用混合模式传入参数值,需要注意的是:在某个位置使用"指定名称传递"方式传入参数值后,其后面的参数值也要使用"指定名称传递",因为"指定名称传递"方式有可能已经破坏了参数原始的定义顺序。

(2)OUT模式参数

这是一种输出类型的参数,表示这个参数在存储过程中已经赋值,并且这个参数值可以传递到当前存储过程以外的环境中,关键字OUT位于参数名称之后。

创建一个存储过程,要求定义两个OUT模式的字符类型的参数,然后将在dept表中检索到的一行部门信息存储到这两个参数中,代码如下:

SQL> create or replace procedure select_dept(num_deptno in number,                -- 定义IN模式变量,要求输入部门编号var_dname out dept.dname%type,       -- 定义OUT模式变量,可以存储部门名称并输出var_loc out dept.loc%type) isbeginselect dname,locinto var_dname,var_locfrom deptwhere deptno=num_deptno;           -- 检索某个部门编号的部门信息exceptionwhen no_data_found then                -- 若SELECT语句无返回记录dbms_output.put_line('该部门的编号不存在');   -- 输出信息end select_dept;/

在上面的存储过程(即select_dept)中,定义了两个OUT参数,由于存储过程要通过OUT参数返回值,所以当调用或执行这个存储过程时,都需要定义变量来保存这两个OUT参数值。

-- 在PL/SQL块中调用OUT模式的存储过程。这种方式需要在PL/SQL块的DECLARE部分定义和存储过程中OUT参数兼容若干变量。

首先在PL/SQL块中声明若干变量,然后调用select_dept存储过程,并将定义的变量传入该存储过程,以便接收OUT参数的返回值,代码如下:

SQL> set serverout on
SQL> declarevar_dname dept.dname%type;var_loc dept.loc%type;beginselect_dept(99,var_dname,var_loc);dbms_output.put_line(var_dname || '位于:' || var_loc);end;/

在上面的代码中,把声明的两个变量传入到存储过程中,当存储过程执行时,其中的OUT参数会被赋值,当存储过程执行完毕,OUT参数的值会在调用处返回,这样定义的两个变量就可以得到OUT参数被赋予的值,最后这两个值就可以在存储过程外任意使用了。

-- 使用EXEC命令执行OUT模式的存储过程。使用EXEC命令需要在SQL*Plus环境中使用VARIABLE关键字声明两个变量,用以存储OUT参数的返回值。

使用VARIABLE关键字声明两个变量,分别用来存储部门名称和位置信息,然后使用EXEC命令执行存储过程,并传入生命的两个变量来接收OUT参数的返回值。代码如下:

SQL> variable var_dname varchar2(50);
SQL> variable var_loc varchar2(50);
SQL> exec select_dept(15,:var_dname,:var_loc);但是通过上面的代码的执行结果,用户看不到变量var_dname和var_Loc的值,这时用户可以通过PRINT命令或SELECT语句来输出变量的值。a. 使用PRINT命令打印输出绑定的变量值。使用PRINT命令打印输出变量var_dname和var_loc的值,代码如下:SQL> print var_dname var_loc;b. 使用SELECT语句检索并输出变量var_dname和var_loc的值,代码如下:SQL> select :var_dname,:var_loc from dual;

如果在存储过程中声明了OUT模式的参数,则在执行存储过程时,必须为OUT参数提供变量,以便接收OUT参数的返回值,否则,程序执行后将出现错误。

(3)IN OUT 模式参数

在执行存储过程时,IN参数不能够被修改,它只能根据被传入的指定值(或是默认值)为存储过程提供数据,而OUT类型的参数只能等待被赋值,而不能像IN 参数那样为存储过程本身提供数据。
但 IN OUT 参数可以兼顾其他两种参数的特点,在调用存储过程时,可以从外界向该类型的参数传入值;在执行完存储过程之后,可以将该参数的返回值传给外界。

创建一个存储过程,其中定义一个IN OUT参数,该存储过程用来计算这个参数的平方或平方根,代码如下:

SQL> create or replace procedure pro_square(num in out number,        -- 计算它的平方或平方根,这是一个IN OUT参数flag in boolean) is        -- 计算平方或平方根的标识,这是一个IN参数i int:=2;                -- 表示计算平方,这是一个内部变量beginif flag then          -- 若为TRUEnum:=power(num,i);    -- 计算平方elsenum:=sqrt(num);     -- 计算平方根end if;end;/

在上面的存储过程中,定义一个 IN OUT 参数,该参数在存储过程被调用时会传入一个数值,然后与另外一个IN参数相结合来判断所进行的运算方式(平方或平方根),最后将计算后的平方或平方根再保存到这个IN OUT参数中。

调用存储过程pro_square,计算某个数的平方或平方根,代码如下:

SQL> set serverout on
SQL> declare var_number number;var_temp number;boo_flag boolean;beginvar_temp:=3;var_number:=var_temp;boo_flag:=false;pro_square(var_number,boo_flag);if boo_flag thendbms_output.put_line(var_temp||'的平方是:'||var_number);elsedbms_output.put_line(var_temp||'的平方根是:'||var_number);end if;end;/

(3)IN 参数的默认值

前面讲到的IN参数的值都是在调用存储过程时传入的,实际上,Oracle支持在声明IN参数的同时给其初始化默认值,这样在存储过程调用时,如果没有向IN参数传入值,则存储过程可以使用默认值进行操作,下面来看一个例子。

创建一个存储过程,定义3个IN参数,并将其中的两个参数设置初始默认值,然后将这3个IN参数的值插入到dept表中,代码如下:

 SQL> create or replace procedure insert_dept(num_deptno in number,var_dname in varchar2 default '综合部',var_loc in varchar2 default '北京') isbegininsert into dept values(num_deptno,var_dname,var_loc); -- 插入一条记录end;/过程已创建。

在上面的存储过程中,IN参数var_dname和var_loc都有默认值,所以在调用insert_dept存储过程时,可以不向这两个参数传入值,而是使用其默认值(当然也可以传入值)。
那么用户可能会产生这样一种困惑,当给一些带有默认值的参数传入值,而对另一些带默认值的参数不传值,并且传值的顺序不固定,该怎么办?对于这种情况,由于顺序不固定,建议使用"指定名称传递"的方式传值,这样程序就不会出现混乱。

在PL/SQL块中调用insert_dept存储过程,并且只向该存储过程传入两个参数值,代码如下:

SQL> set serverout on
SQL> declarerow_dept dept%rowtype;begininsert_dept(57,var_loc=>'太原');commit;select * into row_dept from dept where deptno=57;dbms_output.put_line('部门名称是:《'||row_dept.dname||'》,位置是:《'||row_dept.loc||'》');end;/

在上面的代码中,存储过程insert_dept有3个IN参数,这里只传入两个参数(num_deptno和var_loc)的值,而var_dname参数的值使用默认值'综合部'。

(4)删除存储过程

当一个过程不再被需要时,要将此过程从内存中删除,以释放相应的内存空间,可以使用下面的语句来完成:

 DROP PROCEDURE count_num;

删除存储过程insert_dept,代码如下:

 SQL> drop procedure insert_dept;

当一个存储过程过时,想重新定义时,不必先删除再创建,而只需在CREATE语句后面加上OR REPLACE关键字即可,代码如下:

CREATE OR REPLACE PROCEDURE count_num

2. 函数

函数一般用于计算和返回一个值,可以将经常需要使用的计算或功能写成一个函数。函数的调用是表达式的一部分,而过程的调用是一条PL/SQL语句。

函数与过程在创建的形式上有些类似,也是编译后放在内存中供用户使用,只不过调用函数时要用表达式,而不像过程只需要调用过程名。另外,函数必须要有一个返回值,而过程则没有。

2.1 创建函数

函数的创建语法与存储过程类似,它也是一种存储在数据库中的命名程序块,函数可以接收零或多个输入参数,并且函数必须有返回值(这一点存储过程是没有的),其定义的语法格式如下:

CREATE [OR REPLACE] FUNCTION fun_name(parameter1[,parameter2]...) RETURN data_type IS[inner_variable]
BEGINplsql_sentence;
[EXCEPTION][dowith_sentences;]
END [fun_name];fun_name: 函数名称,如果数据库中已经存在了此名称,则可以指定OR REPLACE关键字,这样新的函数将覆盖掉原来函数。
parameter1: 函数的参数,这是个可选项,因为函数可以没有参数。
data_type: 函数的返回值类型,这是个必选项。在返回值类型的前面要使用RETURN关键字来标明。
inner_variable: 函数的内部变量,它有别于函数的参数,这是个可选项。
plsql_sentence: PL/SQL语句,它是函数主要功能的实现部分,也就是函数主体。
dowith_sentence: 异常处理代码,也是PL/SQL语句,这是一个可选项。

由于函数有返回值,所以在函数主体部分(即BEGIN部分)必须使用RETURN语句返回函数值,并且要求返回值类型要与函数声明时的返回值类型(即data_type)相同。

定义一个函数,用于计算emp表中指定部门的平均工资,代码如下:

SQL> create or replace function get_avg_pay(num_deptno number) return number is   -- 创建一个函数,该函数实现计算某个部门的平均工资,传入部门编号参数num_avg_pay number;    -- 保存平均工资的内部变量beginselect avg(sal) into num_avg_pay from emp where deptno=num_deptno;  -- 某个部门的平均工资return(round(num_avg_pay,2));                               -- 返回平均工资exceptionwhen no_data_found then       -- 若此部门编号不存在                    dbms_output.put_line('该部门编号不存在');return(0);           -- 返回平均工资0end;/

2.2 调用函数

由于函数有返回值,所以在调用函数时,必须使用一个变量来保存函数的返回值,这样函数和这个变量就组成了一个赋值表达式。以上面的get_avg_pay函数为例。

调用函数get_avg_pay,计算部门编号为10的雇员平均工资并输出,代码如下:

SQL> set serveroutput on
SQL> declareavg_pay number;beginavg_pay:=get_avg_pay(10);dbms_output.put_line('平均工资是:'||avg_pay);end;/

2.3 删除函数

删除函数的操作比较简单,使用DROP FUNCTION命令,其后面跟着要删除的函数名称,其语法格式如下:

DROP FUNCTION fun_name;

参数fun_name表示要删除的函数名称。下面以删除get_avg_pay函数为例:

使用DROP FUNCTION命令删除get_avg_pay函数,代码如下:

SQL> drop function get_avg_pay;

当一个函数已经过时,想重新定义时,也不必先删除再创建,同样只需要在CREATE语句后面加上OR REPLACE关键字即可,代码如下:

CREATE OR REPLACE FUNCTION fun_name;

3. 触发器

触发器可以看作是一种"特殊"的存储过程,它定义了一些在数据库相关事件(如INSERT, UPDATE, CREATE等事件)发生时应执行的"功能代码块",通常用于管理复杂的完整性约束,或监控对表的修改,或通知其他程序,甚至可以实现对数据的审计功能。

3.1 触发器简介

在触发器中有一个不得不提的概念--触发事件,触发器正是通过这个"触发事件"来执行的(而存储过程的调用或执行是由用户或应用程序进行的)。

能够引起触发器运行的操作就被称为"触发事件",如执行DML语句(使用INSERT, UPDATE, DELETE语句对表或视图执行数据处理操作),执行DDL语句(使用CREATE, ALTER, DROP语句在数据库中创建,修改,删除模式对象),引发数据库系统事件(如系统启动或退出,产生异常错误等),引发用户事件(如登录或退出数据库操作),以上这些操作都可以引发触发器的运行。

接下来认识一下触发器的语法格式,然后通过这个语法格式再对其中涉及的相关概念进行详细讲解:

CREATE [OR REPLACE] TRIGGER tri_name[BEFORE|AFTER|INSTEAD OF] tri_eventON table_name|view_name|user_name|db_nameFOR EACH ROW [WHEN tri_condition]
BEGINplsql_sentences;
END tri_name;---------- 语法中的关键字
(1)TRIGGER: 表示创建触发器的关键字,就如同创建存储过程的关键字PROCEDURE一样。(2)BEFORE|AFTER|INSTEAD OF: 表示"触发时机"的关键字。BEFORE表示在执行DML等操作之前触发,这种方式能够防止某些错误操作发生而便于回滚或实现某些业务规则;AFTER表示在DML等操作之后发生,这种方式便于记录该操作或某些事后处理信息;INSTEAD OF表示触发器为替代触发器。(3)ON: 表示操作的数据表,视图,用户模式和数据库等,对它们执行某种数据操作(例如对表执行INSERT, ALTER, DROP等操作),将引起触发器的运行。(4)FOR EACH ROW: 指定触发器为行级触发器,当DML语句对每一行数据进行操作时都会引起该触发器的运行。如果未指定该条件,则表示创建语句级触发器,这时无论数据操作影响了多少行,触发器都只会执行一次。--------- 语法中的参数
(1)tri_name: 触发器的名称,如果数据库中已经存在了此名称,则可以指定"or replace"关键字,这样新的触发器将覆盖掉原来的触发器。(2)tri_event: 触发事件,如常用的有 INSERT, UPDATE, DELETE, CREATE, ALTER, DROP等。(3)table_name|view_name|user_name|db_name: 分别表示操作的数据表,视图,用户模式和数据库,对它们的某些操作将引起触发器的运行。(4)WHEN tri_condition: 这是一个触发条件子句,其中WHEN是关键字,tri_condition表示触发条件表达式,只有当该表达式的值为TRUE时,遇到触发事件才会自动执行触发器,使其执行触发操作,否则即便是遇到触发事件也不会执行触发器。(5)plsql_sentences: PL/SQL语句,它是触发器功能实现的主体。

----Oracle的触发事件相对于其他数据库而言比较复杂,例如上面提到过的DML操作,DDL操作,甚至是一些数据库系统的自身事件等都会引起触发器的运行。为此,这里根据触发器的触发事件和触发器的执行情况,将Oracle所支持的触发器分为5种类型:

(1)行级触发器:当DML语句对每一行数据进行操作时都会引起该触发器的运行。

(2)语句级触发器:无论DML语句影响多少行数据,其所引发的触发器仅执行一次。

(3)替换触发器:该触发器是定义在视图上的,而不是定义在表上,它是用来替换所使用实际语句的触发器。

(4)用户事件触发器:是指与DDL操作或用户登录,退出数据库等事件相关的触发器。如用户登录到数据库或使用ALTER语句修改表结构等事件的触发器。

(5)系统事件触发器:是指在Oracle数据库系统的事件中进行触发的触发器,如Oracle实例的启动与关闭。

3.2 语句级触发器

语句级触发器,顾名思义就是针对一条DML语句而引起的触发器执行。在语句级触发器中,不使用FOR EACH ROW子句,也就是说无论数据操作影响多少行,触发器都只会执行一次。
下面通过一系列连续的例子来看一下创建和引发一个语句级触发器的实现过程。

(1)本实例要实现的主要功能是使用触发器在scott模式下针对dept表的各种操作进行监控,为此首先需要创建一个日志表dept_log,它用于存储对dept表的各种数据操作信息,例如操作种类(如插入,修改,删除操作),操作时间等,下面就来创建这个日志信息表。

在scott模式下创建dept_log数据表,并且其中定义两个字段,分别用来存储操作种类信息和操作日期,代码如下:

create table dept_log
(operate_tag varchar2(10),  -- 定义字段,存储操作种类信息operate_time date            -- 定义字段,存储操作日期
)

(2)然后创建一个关于dept表的语句级触发器,将用户对dept表的操作信息保存到dept_log表中,下面就来看这个实例。

创建一个触发器tri_dept,该触发器在INSERT, UPDATE和DELETE事件下都可以被触发,并且操作的数据对象是dept表。然后要求在触发器执行时输出对dept表所做的具体操作,代码如下:

SQL> create or replace trigger tri_deptbefore insert or update or deleteon dept   -- 创建触发器,当dept表发生插入,修改,删除操作时引起该触发器执行。declarevar_tag varchar2(10);  -- 声明一个变量,存储对dept表执行的操作类型beginif inserting then      -- 当触发事件是INSERT时var_tag:='插入';   -- 标识插入操作elsif updating then    -- 当触发事件是UPDATE时var_tag:='修改';   -- 标识修改操作elsif deleting then    -- 当触发事件是INSERT时var_tag:='删除';   -- 标识删除操作end if;insert into dept_log values(var_tag,sysdate);   -- 向日志表中插入对dept表的操作信息end tri_dept;/

对于条件谓词,用户甚至可以还可以在其中判断特定列是否被更新,例如要判断用户是否对dept表中的dname列进行了修改,可以使用下面的语句:

IF updating(dname) THEN  -- 若修改了dept表中的dname列do something about UPDATE dname
END IF;

(3)在创建完触发器之后,接下来就是执行触发器,但它的触发执行与存储过程截然不同,存储过程的执行是由用户或应用程序进行的,而它必须由一定的"触发事件"来诱发执行。例如,对dept表执行插入(INSERT事件),修改(UPDATE事件),删除(DELETE事件)等操作,都会引起tri_dept触发器的运行。

3.3 行级触发器

不言而喻,行级触发器会针对DML操作所影响的每一行数据都执行一次触发器。创建这种触发器时,必须在语法中使用FOR EACH ROW这个选项。
使用行级触发器的一个典型应用就是给数据表生成主键值,下面就来讲解这个典型的应用的实现过程。

(1)为了使用行级触发器生成数据表中的主键值,首先需要创建一个带有主键列的数据表,来看下面的例子。

在scott模式下,创建一个用于存储商品种类的数据表,其中包括商品序号列和商品名称列,代码如下。

SQL> create table goods(id int primary key,good_name varchar2(50));

(2)为了给goods表的id列生成不能重复的有序值,这里需要创建一个序列(一种数据库对象),来看下面的例子。

使用CREATE SEQUENCE语句创建一个序列,命名为seq_id,代码如下。

SQL> create sequence seq_id;
序列已创建。

上面的代码创建了序列seq_id, 用户可以在PL/SQL程序中调用它的NEXTVAL属性来获取一系列有序的数值,这些数值就可以作为goods表的主键值。

(3)在创建了数据表goods和序列seq_id之后,下面来创建一个触发器,用于为goods表的id列赋值。
创建一个行级触发器,该触发器在数据表goods插入数据时被触发,并且在该触发器的主体中实现设置goods表的id列的值,代码如下:

SQL> create or replace trigger tri_insert_goodbefore inserton goods       -- 关于goods数据表,在向其插入新纪录之前,引起该触发器的运行for each row    -- 创建行级触发器beginselect seq_id.nextval    -- 从序列中生成一个新的数值,赋值给当前插入行的id列into :new.idfrom dual;end;/

在上面的代码中,为了创建行级的触发器,使用了FOR EACH ROW选项;为了给goods表的当前插入行的id列赋值,这里使用了:new.id关键字--也称为"列标识符",这个列标识符用来指向新行的id列,给它赋值,就相当于给当前行的id列赋值,下面对这个"列标识符"相关的知识进行讲解。

在行级触发器中,可以访问当前正在受到影响(添加,删除,修改等操作)的数据行,这就可以通过"列标识符"来实现。
列标识符可以分为"原值标识符"和"新值标识符",原值标识符用于标识当前行某个列的原始值,记作:old.column_name(如:old.id),通常在UPDATE语句中和DELETE语句使用,因为在INSERT语句中新插入的行没有原始值;新值标识符用于标识当前行某个列的新值,记作:new.column_name(如:new.id),通常在INSERT语句和UPDATE语句中被使用,因为DELETE语句中被删除的行无法产生新值。

(4)在触发器创建完毕之后,用户可以通过向goods表中插入数据来验证触发器是否被执行,同时也能验证该行级触发器是否能够使用序列为表的主键赋值。

SQL> insert into goods(good_name) values('苹果');
SQL> insert into goods(id,good_name) values(9,'葡萄');

虽然在第二次插入数据行时指定了id的值(即9),但这并没有起任何作用,这是因为在触发器中将序列seq_id的NEXTVAL属性值赋给了:new.id列的标识符,这个列标识符的值就是当前插入行的id列的值,并且NEXTVAL属性值是连续不间断的。

3.4 替换触发器

替换触发器---即INSTEAD OF触发器,它的"触发时机"关键字是INSTEAD OF而不是BEFORE或AFTER。而其他类型触发器不同的是,替换触发器是定义在视图上的,而不是定义在表上。由于视图是由多个基表连接组成的逻辑结构,所以一般不允许用户进行DML操作(如INSERT, UPDATE, DELETE等操作),这样当用户为视图编写替换触发器后,用户对视图的DML操作实际上就变成了执行触发器中的PL/SQL块,这样就可以通过在替换触发器中编写适当的代码对构成视图的各个基表进行操作。

(1)为了创建并使用替换触发器,首先需要创建一个视图,来看下面的例子。
在system模式下,给scott用户授予create view(创建视图)权限,然后在scott模式下创建一个检索雇员信息的视图,该视图的基表包括dept表(部门表)和emp表(雇员表),代码如下:

SQL> connect system/1qaz@wsx已连接。
SQL> grant create view to scott;授权成功。
SQL>connect soctt/tiger已连接。SQL> create view view_emp_deptas select empno,ename,dept.deptno,dname,job,hiredatefrom emp,deptwhere emp.deptno=dept.deptno;

(2)接下来编写一个关于view_emp_dept视图在INSERT事件中的触发器,来看下面的例子。
创建一个关于view_emp_dept视图的替换触发器,在该触发器的主体中实现向emp表和dept表中插入两行相互关联的数据,代码如下:

SQL> create or replace trigger tri_insert_viewinstead of inserton view_emp_dept   -- 创建一个关于view_emp_dept视图的替换触发器for each row  -- 行级视图declarerow_dept dept%rowtype;beginselect * into row_dept from dept where deptno:=new.deptno;    -- 检索指定部门编号的记录行if sql%notfound then -- 未检索到该部门编号的记录insert into dept(deptno,dname)values(:new.deptno,:new.dname);    -- 向dept表中插入数据end if;insert into emp(empno,ename,deptno,job,hiredate)values(:new.empno,:new.ename,:new.deptno,:new.job,:new.hiredate); -- 向emp表中插入数据end tri_insert_view;/

在上面触发器的主体代码中,如果新插入行的部门编号(deptno)不在dept表中,则首先向dept表中插入关于新部门编号的数据行,然后再向emp表中插入记录行,这是因为emp表的外键值(emp.deptno)是dept表的主键值(dept.deptno).

(3)当触发器tri_insert_view成功创建之后,再向view_emp_dept视图中插入数据时,Oracle就不会产生错误信息,而是引起触发器tri_insert_view的运行,从而实现向emp表和dept表中插入两行数据。

首先向视图view_emp_dept插入一条记录,然后在该视图中检索插入的记录行,代码如下:

SQL> insert into view_emp_dept(empno,ename,deptno,dname,job,hiredate)values(8888,'东方',10,'ACCOUNTING','CASHIER',sysdate);SQL> select * from view_emp_dept where empno=8888;

在上面代码的INSERT语句中,由于在dept表中已经存在部门编码(deptno)为10的记录,所以触发器中的程序只向emp表中插入一条记录;若指定的部门编码不存在,则首先要向dept表中插入一条记录,然后再向emp表中插入一条记录。

3.5 用户事件触发器

用户事件触发器是因进行DDL操作或用户登录,退出等操作而引起运行的一种触发器,引起该类型触发器运行的常见事件包括CREATE, ALTER, DROP,ANALYZE, COMMIT, GRANT, REVOKE, RENAME, TRUNCATE, SUSPEND, LOGON和LOGOFF等。

(1)首先创建一个日志信息表,用于保存DDL操作的信息,示例如下:
使用CREATE TABLE语句创建一个日志信息表,该表保存的日志信息包括数据对象,数据对象类型,操作行为,操作用户和操作日期等,代码如下:

SQL> create table ddl_oper_log
(db_obj_name varchar2(20),  -- 数据对象名称db_obj_type varchar2(20),  - 对象类型oper_action varchar2(20), --具体DDL行为oper_user varchar2(20),        -- 操作用户oper_date date           -- 操作日期
);

(2)创建一个用户触发器,用于将当前模式下的DDL操作信息保存到上面所创建的ddl_oper_log日志信息表中,示例如下:
创建一个关于scott用户的DDL操作(这里包括CREATE,ALTER和DROPO)的触发器,然后将DDL操作的相关信息插入到ddl_oper_log日志表中,代码如下:

SQL> create or replace trigger tri_ddl_operbefore create or alter or dropon scott.schema  -- 在scott模式下,在创建,修改,删除数据对象之前将引发该触发器运行begininsert into ddl_oper_log values(ora_dict_obj_name,   -- 操作的数据对象名称ora_dict_obj_type,  -- 对象类型 ora_sysevent,       -- 系统事件名称ora_login_user,        -- 登录用户sysdate);end;/在上面的代码中,当向日志表ddl_oper_log插入数据时,使用了若干个事件属性,它们各自的含义如下。a. ora_dict_obj_name: 获取DDL操作所对应的数据库对象。
b. ora_dict_obj_type: 获取DDL操作所对应的数据库对象类型。
c. ora_sysevent: 获取触发器的系统事件名。
d. ora_login_user: 获取登录用户名。

通过上面的4个事件属性值和sysdate系统属性就可以将scott用户的DDL操作信息获取出来,最后再把这些信息保存到ddl_oper_log日志表中,以备随时查看。

(3)在创建完触发器之后,为了引起触发器的执行,就要在scott模式下进行DDL操作,示例如下:
在scott模式下,创建一个数据表和一个视图,然后删除视图和修改数据表,最后使用SELECT语句查看ddl_oper_log日志表中的DDL操作信息,代码如下:

SQL> create table tb_test(id number);SQL> create view view_test as select empno,ename from emp;SQL> alter table tb_test add(name varchar2(10));SQL> drop view view_test;SQL> select * from ddl_oper_log;用户scott的DDL操作信息都被存储到ddl_oper_log日志表中,这些信息就是由DDL操作引起触发器运行而保存的日志表中。

3.6 删除触发器

当一个触发器不再使用时,要从内存中删除它。例如

DROP TRIGGER my_trigger;

删除触发器tri_dept,代码如下:

SQL> drop trigger tri_dept;

当一个触发器已经过时,想重新定义时,不必先删除再创建,同样只需在CREATE语句后面加上OR REPLACE关键字即可,代码如下:

CREATE OR REPLACE TRIGGER my_trigger;

4. 程序包

程序包由PL/SQL程序元素(如变量,类型)和匿名PL/SQL块(如游标),命名PL/SQL块(如存储过程和函数)组成。

程序包可以被整体加载到内存中,这样就可以大大加快程序包中任何一个组成部分的访问速度。实际上程序包对于用户来说并不陌生,在PL/SQL程序中使用DBMS_OUTPUT.PUT_lINE语句就是程序包的一个具体应用,其中,DBMS_OUTPUT是程序包,而PUT_LINE就是其中的一个存储过程。程序包通常由规范和包主体组成,下面分别进行讲解。

4.1 程序包规范

该"规范"用于规定在程序包中可以使用哪些变量,类型,游标和子程序(指各种命名的PL/SQL块),需要注意的是程序包一定要在"包主体"之前被创建,其语法格式如下。

CREATE [OR REPLACE] PACKAGE pack_name IS[declare_variable];[declare_type];[declare_cursor];[declare_function];[declare_procedure];
END [pack_name];page_name: 程序包的名称,如果数据库中已经存在了此名称,则可以指定OR REPLACE关键字,这样新的程序包将覆盖掉原来的程序包。
declare_variable: 规范内声明的变量。
declare_type: 规范内声明的类型。
declare_cursor: 规范内定义的游标。
declare_function: 规范内声明的函数,但仅定义参数和返回值类型,不包括函数体。
declare_procedure: 规范内声明的存储过程,但仅定义参数,不包括存储过程主体。

创建一个程序包的"规范",首先在该程序包中声明一个可以获取指定部门的平均工资的函数,然后再声明一个可以实现按照指定比例上调指定职务的工资的存储过程,代码如下:

SQL> create or replace package pack_emp isfunction fun_avg_sal(num_deptno number) return number;  -- 获取指定部门的平均工资procedure pro_regulate_sal(var_job varchar2,num_proportion number);   -- 按照指定比例上调指定职务的工资end pack_emp;/

从上面的代码中可以看到,在"规范"中声明的函数和存储过程只有头部的声明,而没有函数体和存储过程主体,这正是规范的特点。

4.2 程序包的主体

程序包的主体包含了在规范中声明的游标,过程和函数的实现代码,另外,也可以在程序包中的主体中声明一些内部变量。

程序包主体的名称必须与规范的名称相同,这样通过这个相同的名称Oracle就可以将"规范"和"主体"结合在一起组成程序包,并实现一起进行编译代码。在实现函数或存储过程主体时,可以将每一个函数或存储过程作为一个独立的PL/SQL块来处理。

与创建"规范"不同的是,创建程序包主体使用CREATE PACKAGE BODY语句,而不是CREATE PACKAGE语句,创建程序包主体的代码如下。

CREATE [OR REPLACE ] PACKAGE BODY pack_name IS
[inner_variable]
[cursor_body]
[function_title]
{BEGINfun_plsql;
[EXCEPTION][dowith_sentences;]
END [fun_name]}
[procedure_title]
{BEGINpro_plsql;
[EXCEPTION][dowith_sentences;]
END [pro_name]}
...
END [pack_name];pack_name: 程序包的名称,要求与"规范"对应的程序包名称相同。
inner_variable: 程序包主体的内部变量。
cursor_body: 游标主体。
function_title: 从"规范"中引入的函数头部声明。
fun_plsql: PL/SQL语句,这里是函数主要功能的实现部分。从BEGIN到END部分就是函数的BODY。
dowith_sentences: 异常处理语句。
fun_name: 函数的名称。
procedure_title: 从"规范中"引入的存储过程头部声明。
pro_plsql: PL/SQL语句,这里是存储过程主要功能的实现部分。从BEGIN到END部分就是存储过程的BODY。
pro_name: 存储过程的名称。

下面通过一个实例来看一下如何创建一个程序包的"主体"以及如何调用一个完整的程序包。

创建程序包pack_emp的主体,在该主体中实现与"规范"中声明的函数和存储过程对应,代码如下:

SQL> create or replace package body pack_emp isfunction fun_avg_sal(num_deptno number) return number is   -- 引入"规范"中的函数num_avg_sal number;  -- 定义内部变量beginselect avg(sal)into num_avg_salfrom empwhere deptno=num_deptno;  -- 计算某个部门的平均工资return(num_avg_sal);      -- 返回平均工资exceptionwhen no_data_found then       -- 若未发现记录dbms_output.put_line('该部门编号不存在雇员记录');return 0;               -- 返回0end fun_avg_sal;procedure pro_regulate_sal(var_job varchar2,num_proportion number) is -- 引入"规范"中的存储过程beginupdate empset sal=sal*(1+num_proportion)where job=var_job;         -- 为指定的职务调整工资end pro_regulate_sal;end pack_emp;/

在创建了程序包的"规范"和"主体"之后,就可以像普通函数的存储过程和函数一样实施调用了。

创建一个匿名的PL/SQL块,然后通过程序包pack_emp调用其中的函数fun_avg_sal和存储过程pro_regulate_sal,并输出函数的返回结果,代码如下:

SQL> set serveroutput on
SQL> declarenum_deptno emp.deptno%type;  -- 定义部门编号变量var_job emp.job%type;        -- 定义职务变量num_avg_sal emp.sal%type;  -- 定义工资变量num_proportion number;     -- 定义工资调整比例变量beginnum_deptno:=10;  -- 设置部门编号为10num_avg_sal:=pack_emp.fun_avg_sal(num_deptno); -- 计算部门编号为10的平均工资dbms_output.put_line(num_deptno||'号部门的平均工资是:'||num_avg_sal);  -- 输出平均工资var_job:='SALESMAN';    -- 设置职务名称num_proportion:=0.1;  -- 设置调整比例pack_emp.pro_regulate_sal(var_job,num_proportion); -- 调整指定部门的工资end;/

至此这个程序包的定义和调用执行完毕。总结一下使用一个程序包的过程就是:首先创建程序包的"规范",然后再创建程序包的"主体",最后在PL/SQL块或SQL*Plus中调用程序包的子程序--即函数或存储过程。

4.3 删除包

与函数和过程一样,当一个包不再使用时,要从内存中删除它,例如:

DROP PACKAGE my_package

删除包pack_emp,代码如下:

SQL> drop package pack_emp;

当一个包已经过时,想重新定义时,不必先删除再创建,同样只需在CREATE 语句后面加上 OR REPLACE关键字即可,例如:

CREATE OR REPLACE PACKAGE my_package

存储过程,函数,触发器的定义及应用,因为这些程序块在应用系统开发中被广泛应用。

Oracle 11g_过程、函数、触发器和包(6)相关推荐

  1. Oracle中过程/函数返回结果集

    http://www.cnitblog.com/wufajiaru/archive/2009/04/28/56796.html Oracle 存储过程返回结果集 关键字: 转载 Sql代码 Oracl ...

  2. PL/SQL编程:过程函数触发器题目分析

    用到的表放到最后了! 过程 编写一个存储过程,能通过"类型名称"直接从商品信息表中获取对应类型的商品数据 题目中的关键字:过程,通过类型名称,直接获取,商品数据 分析:首先理清表中 ...

  3. Oracle编程入门经典 第11章 过程、函数和程序包

    目录 11.1          优势和利益... 1 11.2          过程... 1 11.2.1       语法... 2 11.2.2       建立或者替换... 2 11.2 ...

  4. oracle 触发器 execute immediate,过程、触发器、用户定义函数和批处理中使用的 EXECUTE IMMEDIATE...

    过程.触发器.用户定义函数和批处理中使用的 EXECUTE IMMEDIATE EXECUTE IMMEDIATE 语句允许使用文字字符串(在引号中)和变量的组合来构建语句.例如,以下过程包含创建表的 ...

  5. oracle视图执行脚本,oracle 视图,函数,过程,触发器自动编译脚本

    日常管理维护一个oracle数据库服务器的时,经常会碰到修改view,table结构的情况,而且由于oracle view,函数,存储过程等对象的相互关联的关系,经常会由于一个view,table,f ...

  6. oracle触发器函数,oracle 存储过程、函数和触发器用法实例详解

    本文实例讲述了oracle 存储过程.函数和触发器用法.分享给大家供大家参考,具体如下: 一.存储过程和存储函数 指存储在数据库中供所有用户程序调用的子程序叫存储过程.存储函数. 创建存储过程 用CR ...

  7. Oracle 存储过程,函数和包。

    1. 存储过程和函数 1.1 创建和删除存储过程             创建存储过程,需要有CREATE PROCEDURE 或 CREATE ANY PROCEDURE的系统权限. 基本语法如下: ...

  8. (2)存储过程中可以调用其他存储过程吗?_详解Oracle创建存储过程、创建函数、创建包及实例演示...

    概述 说句实在的,平时工作基本上不会去背啥创建存储过程.创建函数.创建包之类的语法,但是相信大家面试啥的却基本会笔试这些,所以就对存储过程.函数和包的语法做下总结,也做个备忘!这里面语法大家理解就可以 ...

  9. oracle存储过程导出查询结果,ORACLE如何实现函数、包、存储过程的导入和导出

    建 议可以用常规的检查,检查一下:数据字典信息/exp 导出结构检查 1.检查 SELECT * FROM ALL_SOURCE t WHERE T.OWNER = '要查询用户' AND t.TYP ...

最新文章

  1. oracle技术之Oracle 跟踪事件(一)
  2. java 类型转换的原理
  3. display:inline、block、inline-block
  4. web.config中httpRunTime的属性(转)
  5. idea集成scala插件
  6. 内网访问不到内网网站问题和不用端口号访问网站问题
  7. LINQ :最终统治了​所有的语言!
  8. 现代软件工程讲义 个人项目和结对项目练习 地铁
  9. 如何获取cookie值
  10. 从零开始学前端:定时器、Math对象 --- 今天你学习了吗?(JS:Day12)
  11. iOS开发笔记 基于wsdl2objc调用asp.net WebService
  12. PIE SDK矢量点生成等值线、面
  13. Springboot 默认加载文件(可直接访问、不可直接访问)是出现的问题
  14. python 分页插件
  15. 常用 Jacobi 行列式 | 重积分变量替换
  16. html 字体图标大小,CSS ICONFONT 基线和大小问题
  17. PMP 考点 第六章 项目进度管理
  18. 设置idea类注释模板
  19. 互动投影游戏加密狗复制教程!
  20. pandas学习之excel重复项判断显示与去重

热门文章

  1. 10月,我们准备好了!2021全国跨境电商创新创业大赛全国总决赛晋级名单出炉!
  2. admui 框架源码 更新日志
  3. 微信打开链接出现“已停止访问该网页”怎么解决?Mindjump完美解决该问题
  4. 论文笔记-SSF-DAN: Separated Semantic Feature based Domain Adaptation Network for Semantic Segmentation
  5. 常见的撞库及防撞库方案
  6. 【51nod】【单调栈】扔盘子
  7. 程序员入门的第一个程序,打印输出 “ HelloWorld “
  8. 普乐蛙飞行影院设备360°全沉浸式裸眼3D影院
  9. 判别分析原理及R语言实现
  10. 互联网产业:十年成就“中国式发展”