编程实验
触发器与存储过程非常相似,触发器也是SQL语句集,两者唯一的区别是触发器不能用EXECUTE语句调用,而是在用户执行Transact-SQL语句时自动触发(激活)执行。触发器是在一个修改了指定表中的数据时执行的存储过程。通常通过创建触发器来强制实现不同表中的逻辑相关数据的引用完整性和一致性。由于用户不能绕过触发器,所以可以用它来强制实施复杂的业务规则,以确保数据的完整性。触发器不同于存储过程,触发器主要是通过事件执行触发而被执行的,而存储过程可以通过存储过程名称名字而直接调用。当对某一表进行诸如UPDATE、INSERT、DELETE这些操作时,SQLSERVER就会自动执行触发器所定义的SQL语句,从而确保对数据的处理必须符合这些SQL语句所定义的规则。
触发器的主要作用是其能够实现由主键和外键所不能保证的复杂的参照完整性和数据的一致性。它能够对数据库中的相关表进行级联修改,强制比CHECK约束更复杂的数据完整性,并自定义操作消息,维护非规范化数据以及比较数据修改前后的状态。与CHECK约束不同,触发器可以引用其它表中的列。在下列情况下使用触发器实现复杂的引用完整性;强制数据间的完整性。创建多行触发器,当插入,更新、删除多行数据时,必须编写一个处理多行数据的触发器。执行级联更新或级联删除这样的动作。级联修改数据库中所有相关表。撤销或者回滚违反引用完整性的操作,防止非法修改数据。
触发器与存储过程的主要区别在于触发器的运行方式。存储过程必须有用户、应用程序或者触发器来显示的调用并执行,而触发器是当特定时间出现的时候,自动执行或者激活的,与连接用数据库中的用户、或者应用程序无关。当一行被插入、更新或者删除时触发器才执行,同时还取决于触发器是怎样创建的,当UPDATE发生时使用一个更新触发器,当INSERT发生时使用一个插入触发器,当DELETE发生时使用一个删除触发器。
§1 触发器类型
§1.1 DML触发器
Oracle可以在DML语句进行触发,可以在DML操作前或操作后进行触发,并且可以对每个行或语句操作上进行触发。
§1.2替代触发器
由于在Oracle里,不能直接对由两个以上的表建立的视图进行操作。所以给出了替代触发器。它就是Oracle8专门为进行视图操作的一种处理方法。
§1.3系统触发器
Oracle8i 提供了第三种类型的触发器叫系统触发器。它可以在Oracle数据库系统的事件中进行触发,如Oracle系统的启动与关闭等。
§2 创建触发器
创建触发器的一般语法是:
CREATE [ OR REPLACE]TRIGGER trigger_name
[ BEFORE|AFTER ]trigger_event ON table_reference
[ FOR EACH ROW [WHEN trigger_condition] ]
trigger_body;
当一个基表被修改( 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
§3 创建DML触发器
触发器名与过程名和包的名字不一样,它是单独的名字空间,因而触发器名可以和 表 或过程 有相同的名字,但在一个模式中触发器名不能相同。
触发器的限制
触发器有下面一些限制:
。触发器中不能使用控制语句 COMMIT,ROLLBACK, SVAEPOINT 语句;
。由触发器所调用的过程或函数也不能使用控制语句;
。触发器中不能使用LONG,LONG RAW 类型;
。触发器所访问的表受到远表的约束限制,即后面的“变化表”。
问题:当触发器被触发时,要使用被插入,更新或删除的记录中的列值,有时要使用操作前,
后列的值。
实现: :new 修饰符访问操作完成后列的值
:old 修饰符访问操作完成前列的值
例1: 建立一个触发器,当职工表 emp 表被删除一条记录时,把被删除记录写到职工表删除日志表中去。
create or replace trigger scott.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;
/
show errors
§4 创建替代(Instead_of)触发器
Instead_of 用于对视图的DML触发,由于视图有可能是由多个表进行联结(join)而成,因而并非是所有的联结都是可更新的。但可以按照所需的方式执行更新,例如下面情况:
--节选自在线代码 instead.sql
CREATE VIEW room_summary AS
SELECT building,sum(number_seats) total_seats
FROM rooms GROUP BY building;
在此视图中直接删除是非法的:
SQL》DELETE FROM rooms_summary WHERE building=’Building 7’;
DELETE FROM rooms_summary WHERE building=’Building 7’;
*
ERROR at line 1:
ORA-01732:data manipulation operation not legal on this view
但是我们可以创建Instead_of 触发器来为 DELETE 操作执行所需的处理,即删除rooms 表中所有基准行:
--节选自在线代码 instead.sql
CREATE TRIGGER room_summary_delete
INSTEAD OF DELETE ON room_summary
FOR EACH ROW
BEGIN
-- 删除表 room 中行,这些行构成单个视图行。
DELETE FROM rooms WHERE building = :old.building;
END room_summary_delete;
§5 创建系统触发器
Oracle8i提供的系统触发器可以在DDL或数据库系统上被触发。DDL指的是数据定义语言,如CREATE ,ALTER及DROP 等。而数据库系统事件包括数据库服务器的启动或关闭,用户的登录与退出、数据库服务错误等。创建系统触发器的语法如下:
CREATE OR REPLACE TRIGGER [sachema.] trigger_name
{BEFORE|AFTER}
{ddl_event_list|database_event_list}
ON { DATABASE | [schema.] SCHEMA }
[ when_clause] trigger_body;
ddl_event_list: 一个或多个DDL 事件,事件间用 OR 分开;
database_event_list: 一个或多个数据库事件,事件间用 OR 分开;
下面给出系统触发器的种类和事件出现的时机(前或后):
系统触发器可以在数据库级(database)或模式(schema)级进行定义。数据库级触发器在任何事件都激活触发器,而模式触发器只有在指定的模式的触发事件发生时才触发。
例:建立一个当用户USERA登录时,自动记录一些信息的触发器:
CREATE OR REPLACE TRIGGER loguserAconnects
AFTER LOGON ON SCHEMA
BEGIN
INSERT INTO example.temp_table
VALUES(1,’LogUserAConnects fired!’);
END loguserAconnects;
例:建立一个当用户USERB登录时,自动记录一些信息的触发器:
CREATE OR REPLACE TRIGGER loguserAconnects
AFTER LOGON ON SCHEMA
BEGIN
INSERT INTO example.temp_table
VALUES(2,’LogUserAConnects fired!’);
END loguserBconnects;
例:建立一个当所有用户登录时,自动记录一些信息的触发器:
CREATE OR REPLACE TRIGGER logALLconnects
AFTER LOGON ON SCHEMA
BEGIN
INSERT INTO example.temp_table
VALUES(3,’LogUserAConnects fired!’);
END logALLconnects;
SQL》connect usera/usera
Connected.
SQL》connect userb/userb
Connected.
SQL》connect scott/tiger
Connected.
SQL》select * from temp_table;
Num_COL CHAR_COL
-------------- --------------------------------
3 LogALLConnects fired!
2 LoguserBConnects fired!
3 LogALLConnects fired!
3 LogALLConnects fired!
1 LoguserAConnects fired!
§6 触发器触发次序
Oracle 对事件的触发共有16种,但是它们的触发是有次序的,基本触发次序如下:
1) 执行 BEFORE语句级触发器;
2) 对与受语句影响的每一行:
a) 执行 BEFORE语句行级触发器
b) 执行 DML语句
c) 执行 AFTER行级触发器
3)执行 AFTER语句级触发器
§7 使用触发器谓词
ORACLE 提供三个参数 INSERTING,UPDATEING,DELETING 用于判断触发了哪些操作。谓词的行为如下:
例
--节选自在线代码 Rschange.sql
REM 选自:RSchange.sql
REM 作者: Scott Urman.
REM 中文注释:赵元杰
CREATE OR REPLACE TRIGGER LogRSChanges
BEFORE INSERT OR DELETE OR UPDATE ON registered_students
FOR EACH ROW
DECLARE
v_ChangeType CHAR(1);
BEGIN
/* INSERT 用’I’, DELETE用’D’, UPDATE 用’U’ */
IF INSERTING THEN
v_ChangeType := ‘I’;
ELSIF UPDATING THEN
v_ChangeType := ‘U’;
ELSE
v_ChangeType := ‘D’;
END IF;
/* 在RS_audit 记录所有的改变,使用sysdate 来产生系统时间邮戳,
使用 user 返回当前用户的标识 */
INSERT INTO RS_audit
(change_type, changed_by, timestamp,
old_student_id, old_department, old_course, old_grade,
new_student_id, new_department, new_course, new_grade)
VALUES
(v_ChangeType, USER, SYSDATE,
:old.student_id, :old.department, :old.course, :old.grade,
:new.student_id, :new.department, :new.course, :new.grade);
END LogRSChanges;
/
§8 删除和使能触发器
当触发器创建完成后,程序员和DBA管理员要经常关心数据库实例中的触发器的情况。对于不必需的触发器,要进行删除或使触发器无效,从而使系统的性能有所提高。
删除触发器的命令语法如下:
DROP TRIGGER trigger_name;
例:从数据子字典中删除某个触发器:
SQL》 select trigger_name from user_triggers;
TRIGGER_NAME
------------------------------
SET_NLS
SQL》 drop trigger set_nls;
触发器已丢弃
使触发器无效的命令是ALTER TRIGGER,它的语法如下:
ALTER TRIGGER triiger_name [DISABLE | ENABLE ];
如:
SQL》 ALTER TRIGGER updatemajorstats DISABLE;
SQL》 alter table students disable all triggers;
§9 创建触发器的限制
编写触发器程序时有些限制,希望程序人员注意下面的一些情况:
1.代码大小:
一般的触发器的代码大小必须小于32K;如果大于这个限制,可以将其拆成几个部分来写。
2.触发器中有效的语句:
可以包括DML SQL语句,但不能包括DDL 语句。ROLLBACK, COMMIT, and SAVEPOINT也不能使用。但是,对于“系统触发器(system triggers)”可以使用CREATE/ALTER/DROP TABLE和Alter … COMPILE语句。
3. LONG, LONG RAW和LOB的限制:
l 不能插入数据到LONG或LONG RAW;
l 来自LONG或LONG RAW的数据可以转换成字符型(如CHAR和VARCHAR2),但是只允许32KB;
l 使用LONG或LONG RAW不能声明变量;
l 在LONG或LONG RAW列中不能用:NEW 和 :PARENT;
l LOB中的:NEW变量不能修改,例如:
:NEW.Column := 。。。
4. 引用包变量的限制:
如果UPDATE或DELETE语句测到与当前的UPADTE冲突,则Oracle执行ROLLBACK到SAVEPOINT上并重新启动更新。这样可以要出现多次才能成功。
现有字典表(Dict)
需求一:当新增一条记录的时候,若已存在相同键的,拒绝插入
//操作步骤:展开相关表,右击‘触发器’,新建即可
USE [sqlffwj]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo]。[CheckKeyRepeated]
ON [dbo]。[Dict]
for INSERT
AS
if(select COUNT(*) from [Dict], inserted inobj where [Dict].ItemKey = inobj.ItemKey and [Dict].Id != inobj.Id) 》 0
BEGIN
raiserror(‘已有相同键,不能插入’,16,1)
rollback tran
END
需求二:当删除一条记录的时候,若有下层记录,拒绝删除
USE [sqlffwj]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo]。[CheckDependence]
ON [dbo]。[Dict]
for delete
AS
if(select COUNT(*) from [Dict], deleted delbj where [Dict].UpperId = delbj.Id) 》 0
BEGIN
raiserror(‘有下层记录,不能删除’,16,1)
rollback tran
END
需求三:当删除一条记录的时候,若有下层记录,下层也一起删除
USE [sqlffwj]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo]。[CheckDependence2]
ON [dbo]。[Dict]
AFTER delete
AS
while(select COUNT(*) from [Dict] where UpperId != 0 and UpperId not in (select Id from [Dict])) 》 0
BEGIN
delete from [Dict]
where UpperId != 0 and UpperId not in (select Id from [Dict])
END
现有用户表(User)
需求一:用存储过程查询所有用户的信息(标示、姓名、年龄、部门名)
//操作步骤:展开数据库,再展开可编程性,右击‘存储过程’,新建即可
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo]。[GetUsers]
AS
BEGIN
select [User].Id ‘标示’,[User].Name ‘姓名’, [User].Age ‘年龄’, [Dept].Name ‘部门’ from [User] left join [Dept] on [User].DeptId = [Dept].Id
END
GO
/* 调用 */
exec GetUsers
需求二:用存储过程查询指定部门的用户信息(标示、姓名、年龄、部门名)
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo]。[GetUsersByDept]
(
@deptid int /* 部门标示 */
)
AS
BEGIN
select [User].Id ‘标示’,[User].Name ‘姓名’, [User].Age ‘年龄’, [Dept].Name ‘部门’
from [User] left join [Dept] on [User].DeptId = [Dept].Id
where [Dept].Id = @deptid
END
GO
/* 调用 */
exec GetUsersByDept 2
需求三:在项目中用ADO调用存储过程‘GetUsersByDept’
1、环境:VS2010+sql2008
2、新建edmx文件,引用两张表和存储过程
3、切换到‘模型浏览器’,‘添加函数导入’
4、在‘添加函数导入’面板,点击‘获取列信息’,获取到列信息后再点击‘创建新的复杂类型’,确定后就可以通过Func调用存储过程了
5、调用代码Demo
using (var context = new SqltestEntities())
{
var result = context.GetUsersByDept(2);
throw new Exception(result.Count().ToString());
}
全部0条评论
快来发表一下你的评论吧 !