常见的触发器有三种:分别应用于Insert , Update , Delete 事件。(SQL Server 2000定义了新的触发器,这里不提)
StudentID int primary key, --学号
....
)
BorrowRecord int identity(1,1), --流水号
StudentID int , --学号
BorrowDate datetime, --借出时间
ReturnDAte Datetime, --归还时间
...
)
1.如果我更改了学生的学号,我希望他的借书记录仍然与这个学生相关(也就是同时更改借书记录表的学号);
2.如果该学生已经毕业,我希望删除他的学号的同时,也删除它的借书记录。
等等。
On Student
for Update
-------------------------------------------------------
--Name:truStudent
--func:更新BorrowRecord 的StudentID,与Student同步。
--Use :None
--User:System
--Author: 懒虫 # SapphireStudio ([url]www.chair3.com[/url])
--Date : 2003-4-16
--Memo : 临时写写的,给大家作个Sample。没有调试阿。
-------------------------------------------------------
As
if Update(StudentID)
begin
Set br.StudentID=i.StudentID
From BorrowRecord br , Deleted d ,Inserted i
Where br.StudentID=d.StudentID
理解触发器里面的两个临时的表:Deleted , Inserted 。注意Deleted 与Inserted分别表示触发事件的表“旧的一条记录”和“新的一条记录”。
一个Update 的过程可以看作为:生成新的记录到Inserted表,复制旧的记录到Deleted表,然后删除Student记录并写入新纪录。
Create trigger trdStudent
On Student
for Delete
-------------------------------------------------------
--Name:trdStudent
--func:同时删除 BorrowRecord 的数据
--Use :None
--User:System
--Author: 懒虫 # SapphireStudio ([url]www.chair3.com[/url])
--Date : 2003-4-16
--Memo : 临时写写的,给大家作个Sample。没有调试阿。
-------------------------------------------------------
As
Delete BorrowRecord
From BorrowRecord br , Delted d
Where br.StudentID=d.StudentID
这里我们只讲解最简单的触发器。复杂的容后说明。
事实上,我不鼓励使用触发器。触发器的初始设计思想,已经被“级联”所替代。
2.游标
在SQL 2000之前,游标可谓是SQL Server心中的痛: 老牛般的速度(CPU),河马般的胃口(内存)。可你却不能不用他。
什么叫游标呢?说白了就是像高级语言一样,是存放数据集,并逐条访问的一种机制。
比如在Delphi里面,要实现类似于这样的功能:(呵呵,不好意思,我只会Delphi,所以只能举一个Delphi的例子)
//这是一段Delphi的源代码
adoDataSet1.Close;
adoDataSet1.CommandText:=' Select * From Student order by StudentID ';
adoDataSet1.Open;
While Not adoDAtaSet1.Eof Do
Begin
YourVar:=adoDAtaSet1.FieldByName('StudentID').AsInteger;
DoSomeThing();
....
adoDataSet1.Next;
End
在SQL Server 并没有很好的数据逐条访问机制,如果有,那就是游标。
BorrowRecord int identity(1,1), --流水号
StudentID int , --学号
StudentFeeID int , --费用结算号 (外键)
BorrowDate datetime, --借出时间
ReturnDAte Datetime, --归还时间
Fee Money --借书费用
...
)
StudentFeeID int primarykey , --费用结算号 (主键)
StudentID int , --学号
BorrowBookAllFee Money, --所有借书总费用
...
)
-------------------------------------------------------
--Name:一部分代码
--func:更新学生借书总费用
--Use :
--User:
--Author: 懒虫 # SapphireStudio ([url]www.chair3.com[/url])
--Date : 2003-4-16
--Memo : 临时写写的,给大家作个Sample。没有调试阿。
-------------------------------------------------------
Declare curStudentFee Cursor
for
Select StudentFeeID From StudentFee
Declare @mBorrowBookAllFee Money --总费用
Declare @iStudentFeeID Int --借书结算号
Set @mBorrowBookAllFee=0
Set @iStudentFeeID=0
Open curStudentFee
Fetch Next From curStudentFee Into @iStudentFeeID
While ( @@Fetch_Status=0 )
begin
Select @mBorrowBookAllFee=Sum(BorrowBookAllFee)
From BorrowRecord
Where StudentFeeID=@iStudentFeeID
Update StudentFee Set BorrowBookAllFee=@mBorrowBookAllFee
Where StudentFeeID=@iStudnetFeeID
end
Close curStudentFee
Deallocate curStudentFee
关注游标的要点:1、声明、打开、关闭、释放 ; 2、@@Fetch_Status 游标提取状态标志,0表示正确
用触发器实现用户登录记录下登录信息(sysprocesses)
如果你非要用触发器,不妨看看select * from sysprocesses的内容。可以给sysprocesses表加个触发器来完成你的要求。如果你用7.0,可以看看Microsoft提供的sp_who的过程!如下:
create procedure sp_who --- 1995/11/28 15:48
@loginame sysname = NULL --or 'active'
as
@spidhigh int,
@spid int,
@sid varbinary(85)
,@spidhigh = 32767
if ( @loginame is not NULL
AND upper(@loginame) = 'ACTIVE'
)
begin
,loginame=rtrim(loginame)
,hostname ,blk=convert(char(5),blocked)
,dbname= db_name(dbid),cmd
from master.dbo.sysprocesses
where spid >= @spidlow and spid <= @spidhigh AND
upper(cmd) <> 'AWAITING COMMAND'
end
AND upper(@loginame) <> 'ACTIVE'
)
begin
if (@loginame like '[0-9]%') -- is a spid.
begin
select @spid = convert(int, @loginame)
select spid, status,
loginame=rtrim(loginame),
hostname,blk = convert(char(5),blocked),
dbname=db_name(dbid),
cmd
from master.dbo.sysprocesses
where spid = @spid
end
else
begin
select @sid = suser_sid(@loginame)
if (@sid is null)
begin
raiserror(15007,-1,-1,@loginame)
return (1)
end
select spid, status,
loginame=rtrim(loginame),
hostname ,blk=convert(char(5),blocked),
dbname=db_name(dbid),
cmd
from master.dbo.sysprocesses
where sid = @sid
end
return (0)
end
/* loginame arg is null */
select spid,
status,
loginame=rtrim(loginame),
hostname,
blk=convert(char(5),blocked),
dbname=db_name(dbid),
cmd
from master.dbo.sysprocesses
where spid >= @spidlow and spid <= @spidhigh
return (0) -- sp_who
CREATE TRIGGER [TRGJCDNAME] ON dbo.TABLEJCD
FOR INSERT, UPDATE
AS
DECLARE
@CODE VARCHAR(50),
@XS FLOAT,
@ROWCOUNT INT
--检查代码是否为空
//只支持1条操作,如果有多条操作则跳过
IF @@ROWCOUNT>1 RETURN
IF @CODE=''''
BEGIN
ROLLBACK TRAN
RAISERROR(''代码不允许为空或空格!'',16,1)
END
--检查代码是否有重复的
SELECT @ROWCOUNT=COUNT(*) FROM TABLEJCD,INSERTED
WHERE UPPER(TABLEJCD.CODE)=UPPER(INSERTED.CODE) AND (INSERTED.SYSTEMID=TABLEJCD.SYSTEMID)
--如果有重复
IF @ROWCOUNT>1
BEGIN
ROLLBACK TRAN
RAISERROR(''代码重复,请修改!'',16,1)
END
--不允许为0或负数
SELECT @XS=ISNULL(XS,0) FROM INSERTED
IF @XS<=0
BEGIN
ROLLBACK TRAN
RAISERROR(''不允许为0或负数!'',16,1)
END
2.当操作多条记录时(及联删除或更新)
create trigger del_id on tablename for delete
as
delete from tablename where id in(select id from deleted)
3.递规触发器
create tablebudget
(
dept_name varchar(30) not null,
part_name varchar(30) null,
budget_amt money not null)
在上面的表,记录之间是关联,比如部门a,隶属于部门b,顶级部门的父部门为空,我们想到,子部门的预算更新时,其对应的父部门的预算,也要更新
on budget for update as
begin
print ''only on row can be update at a time''
rollback tran
return
end
if (select parent_name from inserted ) is null return
update
set budget_amt=budget_amt+(select budget_amt from inserted)-
(select budget_amt from deleted)
where dept_name=(select parent_name from inserted)
使用嵌套触发器
如果一个触发器在执行操作时引发了另一个触发器,而这个触发器又接着引发下一个触发器……这些触发器就是嵌套触发器。触发器可嵌套至 32 层,并且可以控制是否可以通过"嵌套触发器"服务器配置选项进行触发器嵌套。
如果允许使用嵌套触发器,且链中的一个触发器开始一个无限循环,则超出嵌套级,而且触发器将终止。
可使用嵌套触发器执行一些有用的日常工作,如保存前一触发器所影响行的一个备份。例如,可以在 titleauthor 上创建一个触发器,以保存由 delcascadetrig 触发器所删除的 titleauthor 行的备份。在使用 delcascadetrig 时,从 titles 中删除title_id PS2091 将删除 titleauthor 中相应的一行或多行。要保存数据,可在 titleauthor 上创建 DELETE 触发器,该触发器的作用是将被删除的数据保存到另一个单独创建的名为 del_save 表中。例如:
CREATE TRIGGER savedel
ON titleauthor
FOR DELETE
AS
INSERT del_save
SELECT * FROM deleted
不推荐按依赖于顺序的序列使用嵌套触发器。应使用单独的触发器层叠数据修改。
说明 由于触发器在事务中执行,如果在一系列嵌套触发器的任意层中发生错误,则整个事务都将取消,且所有的数据修改都将回滚。在触发器中包含 PRINT 语句,用以确定错误发生的位置。
递归触发器
触发器不会以递归方式自行调用,除非设置了 RECURSIVE_TRIGGERS 数据库选项。有两种不同的递归方式:
直接递归
即触发器激发并执行一个操作,而该操作又使同一个触发器再次激发。例如,一应用程序更新了表 T3,从而引发触发器 Trig3。Trig3 再次更新表 T3,使触发器 Trig3 再次被引发。
间接递归
即触发器激发并执行一个操作,而该操作又使另一个表中的某个触发器激发。第二个触发器使原始表得到更新,从而再次引发第一个触发器。例如,一应用程序更新了表 T1,并引发触发器 Trig1。Trig1 更新表 T2,从而使触发器 Trig2 被引发。Trig2 转而更新表 T1,从而使 Trig1 再次被引发。
当将 RECURSIVE_TRIGGERS 数据库选项设置为 OFF 时,仅防止直接递归。若要也禁用间接递归,请将 nested triggers 服务器选项设置为 0。
示例
A. 使用递归触发器解决自引用关系
递归触发器的一种用法是用于带有自引用关系的表(亦称为传递闭包)。例如,表 emp_mgr 定义了:
一个公司的雇员 (emp)。
每个雇员的经理 (mgr)。
组织树中向每个经理汇报的雇员总数 (NoOfReports)。
递归 UPDATE 触发器在插入新雇员记录的情况下可以使 NoOfReports 列保持最新。INSERT 触发器更新经理记录的 NoOfReports 列,而该操作递归更新管理层向上其它记录的 NoOfReports 列。
USE pubs
GO
-- Turn recursive triggers ON in the database.
ALTER DATABASE pubs
SET RECURSIVE_TRIGGERS ON
GO
CREATE TABLE emp_mgr (
emp char(30) PRIMARY KEY,
mgr char(30) NULL FOREIGN KEY REFERENCES emp_mgr(emp),
NoOfReports int DEFAULT 0
)
GO
CREATE TRIGGER emp_mgrins ON emp_mgr
FOR INSERT
AS
DECLARE @e char(30), @m char(30)
DECLARE c1 CURSOR FOR
SELECT emp_mgr.emp
FROM emp_mgr, inserted
WHERE emp_mgr.emp = inserted.mgr
OPEN c1
FETCH NEXT FROM c1 INTO @e
WHILE @@fetch_status = 0
BEGIN
UPDATE emp_mgr
SET emp_mgr.NoOfReports = emp_mgr.NoOfReports + 1 -- Add 1 for newly
WHERE emp_mgr.emp = @e -- added employee.
FETCH NEXT FROM c1 INTO @e
END
CLOSE c1
DEALLOCATE c1
GO
-- This recursive UPDATE trigger works assuming:
-- 1. Only singleton updates on emp_mgr.
-- 2. No inserts in the middle of the org tree.
CREATE TRIGGER emp_mgrupd ON emp_mgr FOR UPDATE
AS
IF UPDATE (mgr)
BEGIN
UPDATE emp_mgr
SET emp_mgr.NoOfReports = emp_mgr.NoOfReports + 1 -- Increment mgr's
FROM inserted -- (no. of reports) by
WHERE emp_mgr.emp = inserted.mgr -- 1 for the new report.
UPDATE emp_mgr
SET emp_mgr.NoOfReports = emp_mgr.NoOfReports - 1 -- Decrement mgr's
FROM deleted -- (no. of reports) by 1
WHERE emp_mgr.emp = deleted.mgr -- for the new report.
END
GO
-- Insert some test data rows.
INSERT emp_mgr(emp, mgr) VALUES ('Harry', NULL)
INSERT emp_mgr(emp, mgr) VALUES ('Alice', 'Harry')
INSERT emp_mgr(emp, mgr) VALUES ('Paul', 'Alice')
INSERT emp_mgr(emp, mgr) VALUES ('Joe', 'Alice')
INSERT emp_mgr(emp, mgr) VALUES ('Dave', 'Joe')
GO
SELECT * FROM emp_mgr
GO
-- Change Dave's manager from Joe to Harry
UPDATE emp_mgr SET mgr = 'Harry'
WHERE emp = 'Dave'
GO
SELECT * FROM emp_mgr
GO
以下是更新前的结果:
emp mgr NoOfReports
------------------------------ ----------------------------- -----------
Alice Harry 2
Dave Joe 0
Harry NULL 1
Joe Alice 1
Paul Alice 0
以下为更新后的结果:
emp mgr NoOfReports
------------------------------ ----------------------------- -----------
Alice Harry 2
Dave Harry 0
Harry NULL 2
Joe Alice 0
Paul Alice 0