1.触发器
①含义:
触发器为特殊类型的存储过程,可在执行SQL语句事件时自动生效。
②分类:
SQL Server 包括三种常规类型的触发器:DML 触发器、DDL 触发器和登录触发器。
③创建语法:
IF OBJECT_ID (N'触发器名称', N'TR') IS NOT NULL
DROP TRIGGER 触发器名称
GO
create trigger 触发器名称
on 对应的操作表
for INSERT、UPDATE 或 DELETE
as
T-SQL
go
④常用触发器操作:
-- 1.查询当前数据库下的触发器
select * from sysobjects where xtype='TR'
go
--2.删除触发器:drop trigger --多个已逗号隔开
drop trigger 触发器名称
go
-- 3.重命名触发器名称
exec sp_rename 原名称, 新名称
go
-- 4.禁用触发器:alter table 表名 disable trigger 触发器名称
alter table 表名 disable trigger all --禁用所有触发器
alter table 表名 disable trigger 触发器名称 --禁用指定触发器,多个以逗号相隔
-- 5.启用:alter table 表名 enable trigger 触发器名称
alter table 表名 enable trigger all --禁用所有触发器
alter table 表名 enable trigger 触发器名称 --禁用指定触发器,多个以逗号相隔
-- 6.查看触发器内容
exec sp_helptext '触发器名称'
View Code
2.DML触发器
类别 | 简写 | 含义 |
AFTER(FOR) | for | 用触发器里面的SQL语句替换需要执行的操作,例如Delete |
INSTEAD OF | 无 | 待SQL执行完后才触发触发器中的T-SQL语句 |
3.触发器操作:insert、update、delete
-- CREATE TABLE
IF OBJECT_ID (N'Product') IS NOT NULL
DROP TABLE Product
GO
-- 创建物品信息
CREATE TABLE Product(
productId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
productName VARCHAR(30) NOT NULL,
productCordNo VARCHAR(50) NOT NULL,
productPrice decimal(10,2) NOT NULL,
createtime_product datetime NOT NULL default(getdate()),
last_updatetime datetime NOT NULL
)
GO
-- 创建收费单,关联物品信息ID
IF OBJECT_ID(N'chargeorder') IS NOT NULL
DROP TABLE chargeorder
GO
CREATE TABLE chargeorder (
chargeorderId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
chargeorderPrice decimal(10,2) NOT NULL, --实收物品售价
Number INT CHECK(Number>0), -- 支付数量
Amount as(chargeorderPrice*Number) persisted, --计算列:实收金额
paytime datetime NOT NULL DEFAULT(GETDATE()),-- 收费单支付时间
createtime_chargeorder datetime NOT NULL DEFAULT(GETDATE()), --创建时间
last_updatetime_chargeorder datetime ,--最后更新时间
FK_ProductId INT FOREIGN KEY REFERENCES Product(productId) --创建外键约束
)
GO
IF OBJECT_ID(N'chargeorderdeteli') IS NOT NULL
DROP TABLE chargeorderdeteli
GO
-- 创建收费单详情,关联收费单ID
CREATE TABLE chargeorderdeteli(
chargeorderdeteliId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
chargeorderdeteliName VARCHAR(50) NOT NULL, --对应商品名称
chargeorderdeteliPyType INT NOT NULL DEFAULT('0'), --对应支付类型
chargeorderdeteliPySee INT NOT NULL, -- 账单支付人
createtime_chargeorderdeteli DATETIME NOT NULL DEFAULT(GETDATE()) ,-- 创建时间
last_updatetime_chargeorderdeteli datetime NOT NULL, --最后更新时间
FK_chargeorderId INT FOREIGN KEY REFERENCES chargeorder(chargeorderId) -- 创建外键约束chargeorderId
)
GO
View Code
-- INSERT SQL
-- Product
INSERT INTO Product(productName,productCordNo,productPrice,last_updatetime) VALUES
('健齿白牙膏','20192200898E45',12.00,GETDATE()),
('健齿白牙膏-优惠款','20192200898E50',10.00,GETDATE()),
('黑人牙膏-A款','20192200898E46',24.00,GETDATE()),
('黑人牙膏-B款','20192200898E47',36.00,GETDATE()),
('高露洁牙膏-A款','20192200898E48',18.00,GETDATE()),
('高露洁牙膏-B款','20192200898E49',20.00,GETDATE())
GO
-- chargeorder
INSERT INTO chargeorder(chargeorderPrice,Number,paytime,last_updatetime_chargeorder,FK_ProductId) VALUES
(12.00,3,GETDATE(),GETDATE(),1),
(24.00,3,GETDATE(),GETDATE(),3),
(18.00,3,GETDATE(),GETDATE(),5)
GO
-- chargeorderdeteli
INSERT INTO chargeorderdeteli(chargeorderdeteliName,chargeorderdeteliPyType,chargeorderdeteliPySee,last_updatetime_chargeorderdeteli,FK_chargeorderId) VALUES
('健齿白牙膏',1,219,GETDATE(),1),
('黑人牙膏-A款',3,4990,GETDATE(),2),
('高露洁牙膏-A款',3,4936,GETDATE(),3)
GO
View Code
①触发器-insert
-- 触发器新增:对Product表插入的物品价格进行insert时候触发,价格不能为负数,否则触发器触发回滚,插入失败
-- 触发器新增:对Product表插入的物品价格进行insert时候触发,价格不能为负数,否则触发器触发回滚,插入失败
IF OBJECT_ID(N'tg_insert',N'TR') IS NOT NULL
DROP TRIGGER tg_insert
GO
CREATE TRIGGER tg_insert
ON Product
FOR INSERT
AS
BEGIN
DECLARE @Price decimal(10,2)
SELECT @Price= Product.productPrice FROM Product INNER JOIN inserted ON Product.productPrice=inserted.productPrice
PRINT @Price
IF(@Price<0.00)
BEGIN
RAISERROR('插入的商品价格必须大于0.00',16,8)
ROLLBACK TRAN
END
END
GO
--执行测试SQL:价格为负数
INSERT INTO Product(productName,productCordNo,productPrice,last_updatetime) VALUES
('测试数据1','20192200898E45',-1,GETDATE())
GO
--测试结果:
②触发器-update
-- 触发器更新:更新chargeorder的chargeorderId的同时,更新chargeorderdeteli表FK_chargeorderId,这里属性设定为自增了,所以无法进行修改,其他update操作同理
IF OBJECT_ID(N'tg_update',N'TR') IS NOT NULL
DROP TRIGGER tg_update
GO
CREATE TRIGGER tg_update
ON chargeorder
FOR UPDATE
AS
BEGIN
IF UPDATE(chargeorderId)
UPDATE chargeorderdeteli SET chargeorderdeteli.FK_chargeorderId=inserted.chargeorderId FROM chargeorder,inserted,deleted WHERE chargeorder.chargeorderId=deleted.chargeorderId
IF @@ERROR>0
BEGIN
RAISERROR('操作失败',16,8)
ROLLBACK TRAN
END
END
GO
③触发器-delete
-- 修改原始delete操作,改为update
-- 将原始delete操作修改为update操作
IF OBJECT_ID(N'tg_deleteupdate',N'TR') IS NOT NULL
DROP TRIGGER tg_deleteupdate
GO
CREATE TRIGGER tg_deleteupdate
ON Product
INSTEAD OF DELETE
AS
BEGIN
DECLARE @Product_Id int
set @Product_Id=(SELECT Product.productId FROM Product INNER JOIN deleted ON Product.productId=deleted.productId)
UPDATE Product SET productName='被修改的数据' WHERE Product.productId=@Product_Id
IF @@ERROR>0
BEGIN
RAISERROR('操作失败',16,8)
ROLLBACK TRAN
END
END
GO
-- 原数据形式
-- 执行delete后结果
-- 执行delete操作往Log_delete日志中添加被删除的记录
IF EXISTS(select * from sysobjects where name='Log_delete')
drop table Log_delete
go
create table Log_delete(
logContent varchar(255),
last_updatetime datetime
)
go
View Code
-- 执行删除操作delete触发触发器,向Log_delete表中添加被删除的记录信息
IF OBJECT_ID(N'tg_delete',N'TR') IS NOT NULL
DROP TRIGGER tg_delete
GO
CREATE TRIGGER tg_delete
ON Product
FOR DELETE
AS
BEGIN
DECLARE @Product_Id VARCHAR(255),@TIME DATETIME
SELECT @Product_Id=productId FROM deleted SET @TIME=GETDATE()
INSERT INTO Log_delete(logContent,last_updatetime) VALUES('{被删除的物品Id为:'+@Product_Id+'}',@TIME)
IF @@ERROR>0
BEGIN
RAISERROR('!!!!',16,8)
ROLLBACK TRAN
END
END
GO
-- 测试数据:测试将当前表的前面个tg_deleteupdate触发器禁用
-- 测试结果
4.问题
①多条insert语句一起执行,触发器失效?不做价格控制
②delete插入到log表的时候,删除一个不存在的ID插入空数据?
③使用IN来包含,只记录最前数据
5.对应第4点解决方案
①在insert或者delete的时候,多条并行数据插入(INSERT INTO Product(productName,productCordNo,productPrice,last_updatetime) VALUES('测试数据001','20192200898E49',20,GETDATE()),('测试数据002','20192200898E49',20.00,GETDATE())GO)或者delete(DELETE FROM Product WHERE productId IN(25,26)GO)的方式运行,触发触发器将这部分变动内容写入到log时候只成功第一条返回结果?
-- 处理方法:使用游标
-- 执行删除操作delete触发触发器,向Log_delete表中添加被删除的记录信息,支持含IN作为集合条件
IF OBJECT_ID(N'tg_delete',N'TR') IS NOT NULL
DROP TRIGGER tg_delete
GO
CREATE TRIGGER tg_delete
ON Product
FOR DELETE
AS
BEGIN
DECLARE @Product_Id VARCHAR(255),@TIME DATETIME
DECLARE CUR_DELETE CURSOR FOR SELECT deleted.productId FROM deleted /*声明游标并指明游标操作的对象*/
SELECT @Product_Id=productId FROM deleted
SET @TIME=GETDATE()
OPEN CUR_DELETE -- 打开游标
FETCH NEXT FROM CUR_DELETE INTO @Product_Id --读取下一行
WHILE @@FETCH_STATUS=0
BEGIN
PRINT '@@Start'
INSERT INTO Log_delete(logContent,last_updatetime) VALUES('{被删除的物品Id为:'+@Product_Id+'}',@TIME)
PRINT '@@NEXT PgDn'
FETCH NEXT FROM CUR_DELETE INTO @Product_Id -- 读取下一行
IF @@ERROR>0
BEGIN
RAISERROR('过程错误',16,8)
ROLLBACK TRAN
END
END
CLOSE CUR_DELETE -- 关闭游标
DEALLOCATE CUR_DELETE
PRINT '@@successfully'
END
GO
View Code
-- 测试数据:
-- 测试结果:
-- 日志中添加其他字段信息
-- 执行删除操作delete触发触发器,向Log_delete表中添加被删除的记录信息,支持含IN作为集合条件
IF OBJECT_ID(N'tg_delete',N'TR') IS NOT NULL
DROP TRIGGER tg_delete
GO
CREATE TRIGGER tg_delete
ON Product
FOR DELETE
AS
BEGIN
DECLARE @Product_Id VARCHAR(255),@TIME DATETIME,@ProductName VARCHAR(50)
DECLARE CUR_DELETE CURSOR FOR SELECT productId,productName FROM deleted /*声明游标并指明游标操作的对象*/
--SELECT @Product_Id=productId,@ProductName=productName FROM deleted
SET @TIME=GETDATE()
OPEN CUR_DELETE -- 打开游标
FETCH NEXT FROM CUR_DELETE INTO @Product_Id,@ProductName --读取下一行
WHILE @@FETCH_STATUS=0
BEGIN
PRINT '@@Start'
INSERT INTO Log_delete(logContent,last_updatetime) VALUES('{被删除的物品Id为:'+@Product_Id+','+@ProductName+'}',@TIME)
PRINT '@@NEXT PgDn'
FETCH NEXT FROM CUR_DELETE INTO @Product_Id,@ProductName -- 读取下一行
IF @@ERROR>0
BEGIN
RAISERROR('过程错误',16,8)
ROLLBACK TRAN
END
END
CLOSE CUR_DELETE -- 关闭游标
DEALLOCATE CUR_DELETE
PRINT '@@successfully'
END
GO
View Code