9 子程序
PL/SQL语句块分为匿名和命名两种,匿名语句块每次编译运行且不能存储于数据库中,而且由于没有名称,所以也没法调用。
命名语句块显然具有相反的特点,具体包含过程、函数、包和触发器,其中过程和函数统称为子程序。
9、1 子程序
包含过程和函数
9、1、1 简单使用方法
1)过程的使用
创建为:
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE HelloWorldProc
AS
BEGIN
dbms_output.put_line('Hello World!');
END HelloWorldProc;
/
调用为:
BEGIN
HelloWorldProc;
END;
/
2)函数的使用
和过程很相似,但是具有返回值,可以放入表达式进行调用,创建为:
CREATE OR REPLACE FUNCTION HelloWorldFunc
RETURN varchar IS
BEGIN
return 'Hello World!';
END HelloWorldFunc;
/
使用为:
set serveroutput on size 1000000;
begin
dbms_output.put_line(helloworldfunc());
end;
/
说明:replace表示如果存在对象则覆盖。如果再次编译出错,则对象被删除而没被创建。
9、1、2 子程序参数
9、1、2、1 简单使用
1)具有参数的过程
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE MyProc
(
p_str varchar
)
AS
BEGIN
dbms_output.put_line(p_str);
END MyProc;
/
BEGIN
MyProc('Hello World!');
END;
/
复杂的例子:
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE SelectStudent
(
p_sid in stu.sid%TYPE
)
AS
v_sname varchar(8);
BEGIN
select name into v_sname from stu where sid=p_sid;
DBMS_OUTPUT.PUT_LINE(v_sname);
END SelectStudent;
/
BEGIN
SelectStudent(1);
END;
/
2)具有参数的函数
CREATE OR REPLACE FUNCTION MyFunc
(
p_str varchar
)
RETURN varchar IS
BEGIN
return upper(p_str);
END MyFunc;
/
set serveroutput on size 1000000;
begin
dbms_output.put_line(MyFunc('hello world!'));
end;
/
注意:不能在子程序中约束参数,如长度和精度等,这些约束信息是从实际参数动态获取的。如下面的定义有误:
CREATE OR REPLACE FUNCTION MyFunc
(
p_str varchar(6)
)
RETURN varchar IS
BEGIN
return upper(p_str);
END MyFunc;
/
但是,由于没有形式参数约束,所以在实际参数赋值时可能会产生运行时错误,如:
CREATE OR REPLACE FUNCTION MyFunc
(
p_str out varchar
)
RETURN varchar IS
BEGIN
p_str:='1234567890';
return upper(p_str);
END MyFunc;
/
set serveroutput on size 1000000;
declare
str varchar(6);
begin
dbms_output.put_line(MyFunc(str));
end;
/
错误为:
declare
*
ERROR 位于第 1 行:
ORA-06502: PL/SQL: 数字或值错误 : 字符串缓冲区太小
ORA-06512: 在"SYS.MYFUNC", line 7
ORA-06512: 在line 4
也可以使用%type属性来获取其他变量的类型,虽然在形式参数中可以以此来增加参数约束,但是系统仍然会可能产生运行时错误,如:
CREATE OR REPLACE PACKAGE Globals AS
v_str varchar(6);
END Globals;
/
CREATE OR REPLACE FUNCTION MyFunc
(
p_str out Globals.v_str%type
)
RETURN varchar IS
BEGIN
p_str:='1234567890';
return upper(p_str);
END MyFunc;
/
set serveroutput on size 1000000;
declare
str Globals.v_str%type;
begin
dbms_output.put_line(MyFunc(str));
end;
/
9、1、2、2 参数模式
形式参数具有三种模式,即in,out和in out。默认为in。
一般而言,函数不应该使用out模式,子程序可以使用out模式返回多个形式参数值
1)in模式
在in模式下,形式参数象常量,只读而不能被改变。所以当调用结束,实际参数不变。
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE ModeTest
(
p_Parameter IN NUMBER) IS
v_LocalVariable NUMBER := 0;
BEGIN
IF (p_Parameter IS NULL) THEN
dbms_output.put_line('p_Parameter is NULL');
ELSE
dbms_output.put_line('p_Parameter = ' || p_Parameter);
END IF;
v_LocalVariable := p_Parameter; -- Legal
--p_InParameter := 7; -- Illegal
END ModeTest;
/
set serveroutput on size 1000000;
declare
num number;
BEGIN
num:=1;
ModeTest(num);
dbms_output.put_line(num);
END;
/
2)out模式
在out模式下,实际参数传来的值被忽略,形式参数是个永远未被初始化的变量,具有空值,可读可写,调用结束后,形式参数的值被赋给实际参数。
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE ModeTest
(
p_Parameter OUT NUMBER) IS
v_LocalVariable NUMBER := 0;
BEGIN
IF (p_Parameter IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('p_Parameter is NULL');
ELSE
DBMS_OUTPUT.PUT_LINE('p_Parameter = ' || p_Parameter);
END IF;
/* Assign 7 to p_Parameter. This is legal, since we are writing to an OUT parameter. */
p_Parameter := 7; -- Legal
/* Assign p_Parameter to v_LocalVariable. In Oracle7 version
7.3.4, and Oracle8 version 8.0.4 or higher (including 8i),
this is legal. Prior to 7.3.4, it is illegal to read from an
OUT parameter. */
v_LocalVariable := p_Parameter; -- Possibly illegal
END ModeTest;
/
set serveroutput on size 1000000;
declare
num number;
BEGIN
num:=1;
ModeTest(num);
dbms_output.put_line(num);
END;
/
注意:如果子程序内部发生异常,即便是已经改变形式参数,形式参数的值也不会被赋给实际参数
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE ModeTest
(
p_Parameter OUT NUMBER) IS
v_LocalVariable NUMBER := 0;
BEGIN
IF (p_Parameter IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('p_Parameter is NULL');
ELSE
DBMS_OUTPUT.PUT_LINE('p_Parameter = ' || p_Parameter);
END IF;
/* Assign 7 to p_Parameter. This is legal, since we are writing to an OUT parameter. */
p_Parameter := 7; -- Legal
/* Assign p_Parameter to v_LocalVariable. In Oracle7 version
7.3.4, and Oracle8 version 8.0.4 or higher (including 8i),
this is legal. Prior to 7.3.4, it is illegal to read from an
OUT parameter. */
v_LocalVariable := p_Parameter; -- Possibly illegal
v_LocalVariable:=1/0;
END ModeTest;
/
set serveroutput on size 1000000;
declare
num number;
BEGIN
num:=1;
ModeTest(num);
EXCEPTION
WHEN others THEN
--DBMS_OUTPUT.PUT_LINE(SQLCODE());
--DBMS_OUTPUT.PUT_LINE(substr(SQLERRM(),1,200));
dbms_output.put_line(num);
END;
/
另外,out模式下的实际参数不能是常量或者表达式,因为没法赋值,会导致错误
3)in out模式
是in模式和out模式的组合,实际参数的值可以传给形式参数,同时在调用结束后,形式参数的值也被赋给实际参数。
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE ModeTest
(
p_Parameter IN OUT NUMBER) IS
v_LocalVariable NUMBER := 0;
BEGIN
IF (p_Parameter IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('p_Parameter is NULL');
ELSE
DBMS_OUTPUT.PUT_LINE('p_Parameter = ' || p_Parameter);
END IF;
/* Assign p_Parameter to v_LocalVariable. This is legal, since we are reading from an IN OUT parameter. */
v_LocalVariable := p_Parameter;
/* Assign 7 to p_Parameter. This is legal, since we are writing to an IN OUT parameter. */
p_Parameter := 7;
END ModeTest;
/
set serveroutput on size 1000000;
declare
num number;
BEGIN
num:=1;
ModeTest(num);
dbms_output.put_line(num);
END;
/
9、1、2、3 引用传递和数值传递
缺省情况下,in参数使用引用传递,而in out和out参数使用数值传递
如果显式使用引用传递方式来访问in out和out参数,可以使用NOCOPY编译器提示,此时将会导致形式参数的改变会影响实际参数,甚至在异常发生后也会产生形式参数的改变影响实际参数。
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE ModeTest
(
p_Parameter OUT NOCOPY NUMBER) IS
v_LocalVariable NUMBER := 0;
BEGIN
IF (p_Parameter IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('p_Parameter is NULL');
ELSE
DBMS_OUTPUT.PUT_LINE('p_Parameter = ' || p_Parameter);
END IF;
p_Parameter := 7; -- Legal
v_LocalVariable := p_Parameter;
v_LocalVariable:=1/0;
END ModeTest;
/
set serveroutput on size 1000000;
declare
num number;
BEGIN
num:=1;
ModeTest(num);
EXCEPTION
WHEN others THEN
--DBMS_OUTPUT.PUT_LINE(SQLCODE());
--DBMS_OUTPUT.PUT_LINE(substr(SQLERRM(),1,200));
dbms_output.put_line(num);
END;
/
输出为:
p_Parameter is NULL
7
注意:即便是使用了NOCOPY,并不代表原有的实际参数值会传递到形式参数中,而只能说明形式参数的改变会导致实际参数的改变。
使用NOCOPY方式参数传递速度更快,如:
set serveroutput on size 1000000;
CREATE OR REPLACE PACKAGE CopyFast AS
TYPE StudentArray IS TABLE OF stu%ROWTYPE;
PROCEDURE PassStudents1(p_Parameter IN StudentArray);
PROCEDURE PassStudents2(p_Parameter IN OUT StudentArray);
PROCEDURE PassStudents3(p_Parameter IN OUT NOCOPY StudentArray);
PROCEDURE Go;
END CopyFast;
/
show errors
CREATE OR REPLACE PACKAGE BODY CopyFast AS
PROCEDURE PassStudents1(p_Parameter IN StudentArray) IS
BEGIN
NULL;
END PassStudents1;
PROCEDURE PassStudents2(p_Parameter IN OUT StudentArray) IS
BEGIN
NULL;
END PassStudents2;
PROCEDURE PassStudents3(p_Parameter IN OUT NOCOPY StudentArray) IS
BEGIN
NULL;
END PassStudents3;
PROCEDURE Go IS
v_StudentArray StudentArray := StudentArray(NULL);
v_StudentRec stu%ROWTYPE;
v_Time1 NUMBER;
v_Time2 NUMBER;
v_Time3 NUMBER;
v_Time4 NUMBER;
BEGIN
-- Fill up the array with 50,001 copies of David Dinsmore's
-- record.
SELECT *
INTO v_StudentArray(1)
FROM stu
WHERE sid = 1;
v_StudentArray.EXTEND(1, 1);
-- Call each version of PassStudents, and time them.
-- DBMS_UTILITY.GET_TIME will return the current time, in
-- hundredths of a second.
v_Time1 := DBMS_UTILITY.GET_TIME;
PassStudents1(v_StudentArray);
v_Time2 := DBMS_UTILITY.GET_TIME;
PassStudents2(v_StudentArray);
v_Time3 := DBMS_UTILITY.GET_TIME;
PassStudents3(v_StudentArray);
v_Time4 := DBMS_UTILITY.GET_TIME;
-- Output the results.
DBMS_OUTPUT.PUT_LINE('Time to pass IN: ' || TO_CHAR((v_Time2 - v_Time1) / 100));
DBMS_OUTPUT.PUT_LINE('Time to pass IN OUT: ' || TO_CHAR((v_Time3 - v_Time2) / 100));
DBMS_OUTPUT.PUT_LINE('Time to pass IN OUT NOCOPY: ' || TO_CHAR((v_Time4 - v_Time3) / 100));
END Go;
END CopyFast;
/
show errors
BEGIN
CopyFast.Go;
END;
/
9、1、2、4 没有参数的子程序
如果子程序或者函数没有参数,则在声明和调用时不使用括号,如:
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE NoParamsP AS
BEGIN
DBMS_OUTPUT.PUT_LINE('No Paramaters!');
END NoParamsP;
/
CREATE OR REPLACE FUNCTION NoParamsF
RETURN DATE AS
BEGIN
RETURN SYSDATE;
END NoParamsF;
/
BEGIN
NoParamsP;
DBMS_OUTPUT.PUT_LINE('Calling NoParamsF on ' || TO_CHAR(NoParamsF, 'DD-MON-YYYY'));
END;
/
9、1、2、5 使用命名符
一般的子程序都使用定位符来传递实际参数,为了方便也可以使用命名符来赋值,如:
CREATE OR REPLACE PROCEDURE CallMe
(
p_ParameterA VARCHAR2,
p_ParameterB NUMBER,
p_ParameterC BOOLEAN,
p_ParameterD DATE
)
AS
BEGIN
NULL;
END CallMe;
/
DECLARE
v_Variable1 VARCHAR2(10);
v_Variable2 NUMBER(7,6);
v_Variable3 BOOLEAN;
v_Variable4 DATE;
BEGIN
CallMe( p_ParameterC => v_Variable3, p_ParameterD => v_Variable4,p_ParameterA => v_Variable1, p_ParameterB => v_Variable2);
END;
/
注意
1)可以重新调整参数位置
2)可以混合使用定位符和命名符,但是命名符只能在定位符后使用,如:
DECLARE
v_Variable1 VARCHAR2(10);
v_Variable2 NUMBER(7,6);
v_Variable3 BOOLEAN;
v_Variable4 DATE;
BEGIN
CallMe(v_Variable1, v_Variable2,p_ParameterC => v_Variable3,p_ParameterD => v_Variable4);
END;
/
9、1、2、6 使用参数缺省值
CREATE OR REPLACE PROCEDURE AddNewStudent
(
p_Sid stu.sid%TYPE,
p_Name stu.name%TYPE DEFAULT 'NONE',
p_Sex stu.sex%TYPE DEFAULT 'M'
)
AS
BEGIN
INSERT INTO stu VALUES (p_Sid, p_Name, p_Sex);
END AddNewStudent;
/
BEGIN
AddNewStudent(200,'Simon','M');
AddNewStudent(300);
END;
/
select * from stu;
delete from stu where sid>100
注意:
1)缺省值一般只能从尾到头来定义,调用时的缺省值也只能是最后几个参数,如果跳跃定义,必须通过命名符来访问参数,如:
CREATE OR REPLACE PROCEDURE AddNewStudent
(
p_Sid stu.sid%TYPE,
p_Name stu.name%TYPE DEFAULT 'NONE',
p_Sex stu.sex%TYPE
)
AS
BEGIN
INSERT INTO stu VALUES (p_Sid, p_Name, p_Sex);
END AddNewStudent;
/
BEGIN
AddNewStudent(p_Sid=>400, p_Sex=>'F');
END;
/
2)default关键词可以使用“:=”代替
9、1、3 CALL语句
可以直接在SQL语句块中调用子程序,但是不能在PL/SQL语句块中调用子程序,如果需要在PL/SQL语句块中使用CALL,需要使用动态SQL语句,如:
set serveroutput on size 1000000;
CREATE OR REPLACE PROCEDURE CallProc1(p1 IN VARCHAR2 := NULL) AS
BEGIN
DBMS_OUTPUT.PUT_LINE('CallProc1 called with ' || p1);
END CallProc1;
/
CREATE OR REPLACE PROCEDURE CallProc2(p1 IN OUT VARCHAR2) AS
BEGIN
DBMS_OUTPUT.PUT_LINE('CallProc2 called with ' || p1);
p1 := p1 || ' returned!';
END CallProc2;
/
CREATE OR REPLACE FUNCTION CallFunc(p1 IN VARCHAR2)
RETURN VARCHAR2 AS
BEGIN
DBMS_OUTPUT.PUT_LINE('CallFunc called with ' || p1);
RETURN p1;
END CallFunc;
/
-- Some valid calls direct from SQL.
CALL CallProc1('Hello!');
CALL CallProc1();
VARIABLE v_Output VARCHAR2(50);
CALL CallFunc('Hello!') INTO :v_Output;
PRINT v_Output
CALL CallProc2(:v_Output);
PRINT v_Output
-- This is illegal
--BEGIN
-- CALL CallProc1();
--END;
--/
-- But these are legal
DECLARE
v_Result VARCHAR2(50);
BEGIN
EXECUTE IMMEDIATE 'CALL CallProc1(''Hello from PL/SQL'')';
EXECUTE IMMEDIATE
'CALL CallFunc(''Hello from PL/SQL'') INTO :v_Result' USING OUT v_Result;
END;
/
9、2 包
包是相关对象的共同存储体,包含两个部分,一是包头,即包的说明,另一个是包体,用于定义包中成员。这两个部分是分开定义和存储的。
相对于子程序而言,特点为:
1)封装相关对象
2)包只能存储于全局,不可能为局部
3)依赖性要小于子程序
4)性能较好
9、2、1 定义
9、2、1、1 包头
如:
CREATE OR REPLACE PACKAGE StuPackage AS
PROCEDURE AddStudent(p_SID IN stu.sid%TYPE,p_Name IN stu.name%TYPE,p_sex IN stu.sex%TYPE);
PROCEDURE RemoveStudent(p_SID IN stu.sid%TYPE);
e_StudentNotRegistered EXCEPTION;
TYPE t_StudentIDTable IS TABLE OF stu.sid%TYPE INDEX BY BINARY_INTEGER;
END StuPackage;
/
注意:代码显示红色是因为把RemoveStudent中的Rem当成注释
9、2、1、2 包体
如:
CREATE OR REPLACE PACKAGE BODY StuPackage AS
PROCEDURE AddStudent(p_SID IN stu.sid%TYPE,p_Name IN stu.name%TYPE,p_Sex IN stu.sex%TYPE)
is
begin
insert into stu values(p_SID,p_Name,p_Sex);
end AddStudent;
PROCEDURE RemoveStudent(p_SID IN stu.sid%TYPE)
is
begin
delete from stu where sid=p_SID;
IF SQL%NOTFOUND THEN
RAISE e_StudentNotRegistered;
END IF;
end RemoveStudent;
END StuPackage;
/
说明:
1)包体独立定义和编译,当然要保证包头先行定义和编译
2)包体不是必须的,此时的包充当封装全局变量的作用
3)包体的定义要和包头的定义原型一致
9、2、2 使用包
9、2、2、1 一般使用
begin
StuPackage.AddStudent(900,'胡歌','M');
end;
/
select * from stu;
begin
StuPackage.RemoveStudent(900);
end;
/
select * from stu;
9、2、2、2 包内部成员的使用
上述包中的异常变量可以在包外部使用,如:
set serveroutput on size 1000000;
begin
RAISE StuPackage.e_StudentNotRegistered;
EXCEPTION
WHEN others THEN
DBMS_OUTPUT.PUT_LINE('Error');
end;
/
但是下面包中定义的成员不能在包外部使用,如:
CREATE OR REPLACE PACKAGE StuPackage AS
PROCEDURE AddStudent(p_SID IN stu.sid%TYPE,p_Name IN stu.name%TYPE,p_sex IN stu.sex%TYPE);
PROCEDURE RemoveStudent(p_SID IN stu.sid%TYPE);
TYPE t_StudentIDTable IS TABLE OF stu.sid%TYPE INDEX BY BINARY_INTEGER;
END StuPackage;
/
CREATE OR REPLACE PACKAGE BODY StuPackage
as
e_StudentNotRegistered EXCEPTION;
PROCEDURE AddStudent(p_SID IN stu.sid%TYPE,p_Name IN stu.name%TYPE,p_Sex IN stu.sex%TYPE)
is
begin
insert into stu values(p_SID,p_Name,p_Sex);
end AddStudent;
PROCEDURE RemoveStudent(p_SID IN stu.sid%TYPE)
is
begin
delete from stu where sid=p_SID;
IF SQL%NOTFOUND THEN
RAISE e_StudentNotRegistered;
END IF;
end RemoveStudent;
END StuPackage;
/
此时会出错,如:
set serveroutput on size 1000000;
begin
RAISE StuPackage.e_StudentNotRegistered;
EXCEPTION
WHEN others THEN
DBMS_OUTPUT.PUT_LINE('Error');
end;
/
9、2、2、3 包成员的重载
set serveroutput on size 1000000;
CREATE OR REPLACE TYPE t1 AS OBJECT
(
f NUMBER
);
/
show errors
CREATE OR REPLACE TYPE t2 AS OBJECT
(
f NUMBER
);
/
show errors
CREATE OR REPLACE PACKAGE Overload AS
PROCEDURE Proc(p_Parameter1 IN t1);
PROCEDURE Proc(p_Parameter1 IN t2);
END Overload;
/
show errors
CREATE OR REPLACE PACKAGE BODY Overload AS
PROCEDURE Proc(p_Parameter1 IN t1) IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Proc(t1): ' || p_Parameter1.f);
END Proc;
PROCEDURE Proc(p_Parameter1 IN t2) IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Proc(t2): ' || p_Parameter1.f);
END Proc;
END Overload;
/
show errors
set serveroutput on
DECLARE
v_Obj1 t1 := t1(1);
v_Obj2 t2 := t2(2);
BEGIN
Overload.Proc(v_Obj1);
Overload.Proc(v_Obj2);
END;
/
9、2、2、4 包的初始化
直接在包体中使用begin和end来封装初始化代码,如:
CREATE OR REPLACE PACKAGE Random AS
-- Random number generator. Uses the same algorithm as the
-- rand() function in C.
-- Used to change the seed. From a given seed, the same
-- sequence of random numbers will be generated.
PROCEDURE ChangeSeed(p_NewSeed IN NUMBER);
-- Returns a random integer between 1 and 32767.
FUNCTION Rand RETURN NUMBER;
-- Same as Rand, but with a procedural interface.
PROCEDURE GetRand(p_RandomNumber OUT NUMBER);
-- Returns a random integer between 1 and p_MaxVal.
FUNCTION RandMax(p_MaxVal IN NUMBER) RETURN NUMBER;
-- Same as RandMax, but with a procedural interface.
PROCEDURE GetRandMax(p_RandomNumber OUT NUMBER,
p_MaxVal IN NUMBER);
END Random;
/
CREATE OR REPLACE PACKAGE BODY Random AS
/* Used for calculating the next number. */
v_Multiplier CONSTANT NUMBER := 22695477;
v_Increment CONSTANT NUMBER := 1;
/* Seed used to generate random sequence. */
v_Seed number := 1;
PROCEDURE ChangeSeed(p_NewSeed IN NUMBER) IS
BEGIN
v_Seed := p_NewSeed;
END ChangeSeed;
FUNCTION Rand RETURN NUMBER IS
BEGIN
v_Seed := MOD(v_Multiplier * v_Seed + v_Increment,
(2 ** 32));
RETURN BITAND(v_Seed/(2 ** 16), 32767);
END Rand;
PROCEDURE GetRand(p_RandomNumber OUT NUMBER) IS
BEGIN
-- Simply call Rand and return the value.
p_RandomNumber := Rand;
END GetRand;
FUNCTION RandMax(p_MaxVal IN NUMBER) RETURN NUMBER IS
BEGIN
RETURN MOD(Rand, p_MaxVal) + 1;
END RandMax;
PROCEDURE GetRandMax(p_RandomNumber OUT NUMBER,
p_MaxVal IN NUMBER) IS
BEGIN
-- Simply call RandMax and return the value.
p_RandomNumber := RandMax(p_MaxVal);
END GetRandMax;
BEGIN
/* Package initialization. Initialize the seed to the current
time in seconds. */
ChangeSeed(TO_NUMBER(TO_CHAR(SYSDATE, 'SSSSS')));
END Random;
/
说明:SSSSS意思为“Seconds past midnight”
使用为:
set serveroutput on size 1000000;
declare
p_rand number;
begin
Random.GetRand(p_rand);
DBMS_OUTPUT.PUT_LINE(p_rand);
Random.GetRandMax(p_rand,6);
DBMS_OUTPUT.PUT_LINE(p_rand);
DBMS_OUTPUT.PUT_LINE(Random.rand);
DBMS_OUTPUT.PUT_LINE(Random.randmax(100));
end;
/
9、3 触发器
触发器和子程序相似,区别在于
1)不能定义为局部,子程序可以,方法为:
set serveroutput on size 1000000;
declare
p_rand number;
PROCEDURE NoParamsP as
BEGIN
DBMS_OUTPUT.PUT_LINE('No Paramaters!');
END NoParamsP;
begin
NoParamsP;
end;
/
2)主要通过事件来隐式触发,无需显示调用
3)不接受参数
4)不能对SYS对象做触发器,所以新建新的其他用户表,如:
create table Scott.stu
(
sid integer primary key,
name varchar(8) not null,
sex char(1) check(sex in('M','F'))
);
insert into scott.stu values(1,'Tom','M');
insert into scott.stu values(2,'Alice','F');
insert into scott.stu values(3,'eric','M');
5)触发器不能超过32K,如果更大,需要将代码封装进包或者存储过程,在触发器中调用它们
9、3、1 触发器类型
Oracle的触发器按照触发方式可以分为三种,分别为DML触发器、instead-of触发器和系统触发器。
9、3、1、1 DML触发器
9、3、1、1、1 简单使用
CREATE OR REPLACE TRIGGER scott.OperatingStu
AFTER INSERT OR DELETE OR UPDATE ON scott.stu
BEGIN
DBMS_OUTPUT.PUT_LINE('OK!');
END OperatingStu;
/
观察为:
insert into scott.stu values(100,'Jim','M');
9、3、1、1、2 详细定义
DML触发器的定义有三个要点
1)可以对三种DML操作进行定义,如insert、update和delete
2)可以指定两个时机,如after和before
3)可以指定行级或者语句级,默认为语句级,如为行级,则每条操作记录都会触发一次触发器,如:
CREATE OR REPLACE TRIGGER scott.OperatingStu
AFTER INSERT OR DELETE OR UPDATE ON scott.stu
for each row
BEGIN
DBMS_OUTPUT.PUT_LINE('OK!');
END OperatingStu;
/
此时更新多条记录会导致多次触发,如:
update scott.stu set name=upper(name);
9、3、1、1、3 触发顺序
1)如果同一个表有多个相同触发器,触发的顺序应该是先触发新的触发器,后激活旧的触发器,如:
set serveroutput on size 1000000;
CREATE OR REPLACE TRIGGER scott.OperatingStuAlso
AFTER INSERT OR DELETE OR UPDATE ON scott.stu
for each row
BEGIN
DBMS_OUTPUT.PUT_LINE('OK!!');
END OperatingStuAlso;
/
可以观察为:
update scott.stu set name=upper(name);
2)如果同一个表存在行级和语句级的触发器,触发顺序为(不考虑不存在的情况):
语句级别之前的触发器
每一行之前的触发器
执行语句操作行
每一行之后的触发器
语句级别之后的触发器
9、3、1、1、4 相关标识符
触发器中可以使用:old和:new两个相关标识符,它们是系统提供的PL/SQL绑定变量,分别保存更新前的表记录信息和更新后的表记录信息
注意:
1)可以更新:new,将会改写更新信息,如:
CREATE OR REPLACE TRIGGER GenerateStudentID
BEFORE INSERT OR UPDATE ON scott.stu
FOR EACH ROW
BEGIN
SELECT max(sid)+1 INTO :new.sid FROM scott.stu;
END GenerateStudentID;
/
insert into scott.stu(name,sex) values('Lee','M');
select * from scott.stu;
此时即便是指定sid也会被改写,如:
insert into scott.stu values(1000,'Lee','M');
select * from scott.stu;
2)只能在行级别触发器才能使用:old和:new两个相关标识符,因为语句触发器会影响多行
3):old不能修改,只能够读取,:new只能在行级别之前触发器中被修改,不能在行级别之后触发器中被修改
4):old和:new两个相关标识符都是记录类型,可以理解为triggering_table%rowtype类型,但是实际上不是记录,所以不能作为一般记录来处理,如不能整个记录来进行赋值,不能当作参数赋予子程序
5)对于insert操作的:old和delete操作的:new,相关信息都为null
6)可以使用REFERENCING替换默认的相关标识符名称(注意位置),如:
CREATE OR REPLACE TRIGGER GenerateStudentID
BEFORE INSERT OR UPDATE ON scott.stu
REFERENCING new as new_table
FOR EACH ROW
BEGIN
SELECT max(sid)+1 INTO :new_table.sid FROM scott.stu;
END GenerateStudentID;
/
7)还有:parent相关标识符标识嵌套表所对应的父表当前行
9、3、1、1、5 指定触发的条件(WHEN)
set serveroutput on size 1000000;
CREATE OR REPLACE TRIGGER scott.OperatingStu
AFTER INSERT OR DELETE OR UPDATE ON scott.stu
for each row
when (new.sex='M')
BEGIN
DBMS_OUTPUT.PUT_LINE('OK!');
END OperatingStu;
/
注意:在when语句中的相关标识符不用加冒号
上述触发器只会在操作男生记录时触发,如:
update scott.stu set name=upper(name);
当然,也可以写为:
set serveroutput on size 1000000;
CREATE OR REPLACE TRIGGER scott.OperatingStu
AFTER INSERT OR DELETE OR UPDATE ON scott.stu
for each row
BEGIN
if :new.sex='M' then
DBMS_OUTPUT.PUT_LINE('OK!');
end if;
END OperatingStu;
/
9、3、1、1、6 触发器谓词
通过INSERTING、UPDATING和DELETING函数来判断触发类型,如:
set serveroutput on size 1000000;
CREATE OR REPLACE TRIGGER scott.OperatingStu
AFTER INSERT OR DELETE OR UPDATE ON scott.stu
for each row
BEGIN
if INSERTING then
DBMS_OUTPUT.PUT_LINE('INSERTING');
elsif UPDATING then
DBMS_OUTPUT.PUT_LINE('UPDATING');
elsif DELETING then
DBMS_OUTPUT.PUT_LINE('DELETING');
end if;
END OperatingStu;
/
update scott.stu set name=upper(name);
9、3、1、2 instead-of触发器
用于给视图创建触发器,对于不能直接更新的试图通过此触发器来使之能够更新,如创建新的视图:
create or replace view scott.new_stu
as select upper(name) upper from scott.stu;
select * from scott.new_stu;
此时不能插入更新,如:
insert into scott.new_stu values('Jim');
建立instead-of触发器,如:
create or replace trigger scott.insertView
instead of insert on scott.new_stu
declare
p_num integer;
begin
select max(sid) into p_num from scott.stu;
insert into scott.stu values(p_num+1,:new.upper,'M');
end;
/
注意:所有的instead-of触发器都是行级的,for each row可写可不写
此时可以更新,如:
insert into scott.new_stu values('Jim');
select * from scott.new_stu;
select * from scott.stu;
说明:
视图在没有下面几项内容的时候,通常可以更新:
1)集合操作符(union等)
2)聚合运算函数(sum等)
3)group by,connect by和start with
4)distinct操作符
5)连接
9、3、1、3 系统触发器
9、3、1、3、1 一般使用
当发生诸如数据库关闭或者启动之类的系统事件和执行DDL语句时,激发系统触发器
下面的系统触发器可以记录任何创建的数据字典对象信息,如:
建立信息储存表,如:
CREATE TABLE scott.ddl_creations
(
user_id VARCHAR2(30),
object_type VARCHAR2(20),
object_name VARCHAR2(30),
object_owner VARCHAR2(30),
creation_date DATE
);
创建系统触发器,如:
CREATE OR REPLACE TRIGGER scott.LogCreations
AFTER CREATE ON DATABASE
BEGIN
INSERT INTO ddl_creations (user_id, object_type, object_name,object_owner, creation_date)VALUES (USER, SYS.DICTIONARY_OBJ_TYPE, SYS.DICTIONARY_OBJ_NAME,SYS.DICTIONARY_OBJ_OWNER, SYSDATE);
END LogCreations;
/
创建新表,如:
create table stubak
(
sid integer primary key,
name varchar(8) not null,
sex char(1) check(sex in('M','F'))
);
查询:
select * from scott.ddl_creations;
drop table stubak;
9、3、1、3、2 数据库级别触发器和模式级别触发器
系统触发器可以在数据库级别和模式级别进行定义,其中只要有事件发生就会触发数据库级别触发器,而只有以指定模式发生事件时才会触发模式级别触发器。默认为该用户模式对应的模式触发器。
注意:DML和instead_of触发器没有模式之分
set serveroutput on size 1000000;
建立UserA和UserB帐户
Connect system/system@school AS SYSDBA
CREATE USER UserA IDENTIFIED BY UserA;
GRANT connect, resource, ADMINISTER DATABASE TRIGGER TO UserA;
CREATE USER UserB IDENTIFIED BY UserB;
GRANT connect, resource, ADMINISTER DATABASE TRIGGER TO UserB;
create table temp_table
(
info varchar2(100)
);
grant all on temp_table to UserA;
grant all on temp_table to UserB;
以UserA帐户登录创建模式触发器
connect UserA/UserA
CREATE OR REPLACE TRIGGER LogUserAConnects AFTER LOGON ON SCHEMA
BEGIN
insert into sys.temp_table values('LogUserAConnects fired!');
END LogUserAConnects;
/
以UserB帐户登录创建模式触发器
connect UserB/UserB
CREATE OR REPLACE TRIGGER LogUserBConnects AFTER LOGON ON SCHEMA
BEGIN
insert into sys.temp_table values('LogUserBConnects fired!');
END LogUserBConnects;
/
以system帐户登录创建数据库触发器
Connect system/system@school AS SYSDBA
CREATE OR REPLACE TRIGGER LogAllConnects AFTER LOGON ON DATABASE
BEGIN
insert into sys.temp_table values('LogAllConnects fired!');
END LogAllConnects;
/
以三个帐户分别登录三次
connect UserA/UserA;
connect UserB/UserB;
Connect system/system@school AS SYSDBA;
查看temp_table表信息
select * from temp_table;
输出为:
INFO
-----------------------
LogUserAConnects fired!
LogAllConnects fired!
LogUserBConnects fired!
LogAllConnects fired!
LogAllConnects fired!
9、3、1、3、3 事件属性函数
利用事件属性函数可以得到更多的事件信息,如:
Connect system/system@school AS SYSDBA
CREATE OR REPLACE TRIGGER InfoTrigger AFTER CREATE ON DATABASE
BEGIN
insert into temp_table values(sysevent());
insert into temp_table values(database_name());
insert into temp_table values(login_user());
insert into temp_table values(dictionary_obj_type());
insert into temp_table values(dictionary_obj_name());
insert into temp_table values(dictionary_obj_owner());
END InfoTrigger;
/
delete from temp_table;
create table stutemp
(
sid integer primary key,
name varchar(8) not null,
sex char(1) check(sex in('M','F'))
);
select * from temp_table;
9、3、1、3、4 使用when
CREATE OR REPLACE TRIGGER InfoTrigger AFTER CREATE ON DATABASE
when (dictionary_obj_type()='TABLE')
BEGIN
insert into temp_table values(dictionary_obj_name());
insert into temp_table values(dictionary_obj_owner());
END InfoTrigger;
/
create table stutempalso
(
sid integer primary key,
name varchar(8) not null,
sex char(1) check(sex in('M','F'))
);
select * from temp_table;
9、3、1、3、5 补充
1)触发器可以与子程序和包、表等同名(但是不建议)
2)触发器代码中不能使用任何事务控制语句
3)可以查看数据字典了解触发器的情况,如查看用户和DBA的触发器情况:
select trigger_name,trigger_type,triggering_event from user_triggers;
select trigger_name,trigger_type,triggering_event from dba_triggers;
[此贴子已经被作者于2010-12-11 20:03:49编辑过]