SqlServer INSTEAD OF UPDATE 视图触发器问题

源于系统中的 INSTEAD OF UPDATE 视图触发器关联表更新时,发生了错误如下:

消息 414,级别 16,状态 1,第 1 行

不允许使用 UPDATE,因为该语句会更新视图 "VTestTab",而该视图参与联接并且有一个 INSTEAD OF UPDATE 触发器。

于是测试该触发器的执行原理是什么!~

说明:

视图只能被 INSTEAD OF 触发器引用,每个 INSERT、UPDATE 或 DELETE 语句最多可定义一个 INSTEAD OF 触发器

INSTEAD OF 触发器不可以用于使用 WITH CHECK OPTION 的可更新视图,否则 SQL Server 将引发错误

创建测试环境:

--	DROP TABLE [TestTab]	--	TRUNCATE TABLE [TestTab]
--	创建表
CREATE TABLE [dbo].[TestTab](
	[id] [int] NOT NULL,
	[name] [varchar](50) NOT NULL,
	[insertDate] [datetime] NOT NULL,
	[value] [numeric](14, 4) NULL,
	[info] [varchar](20) NULL,
	CONSTRAINT [PK_TestTab] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

--	创建视图
CREATE VIEW [dbo].[VTestTab]
AS
SELECT [id] ,[name],[insertDate],[value],[info]
FROM [dbo].[TestTab]
GO

--	创建视图更新触发器(主要为这个引发的问题!)
CREATE TRIGGER [dbo].[tgr_VTestTab_update]
ON [dbo].[VTestTab] INSTEAD OF UPDATE
AS
UPDATE [VTestTab] SET
[name] = T2.[name],
[insertDate] = T2.[insertDate],
[value] = T2.[value],
[info] = T2.[info]
FROM [VTestTab] AS t1, inserted AS t2 WHERE t1.id = t2.id
GO

--	插入数据到视图
INSERT INTO [VTestTab]
SELECT 1,'kk',GETDATE(),0,''
UNION ALL
SELECT 2,'JJ',GETDATE(),5,'HH'
UNION ALL
SELECT 3,'SS',GETDATE(),10,''
UNION ALL
SELECT 4,'MM',GETDATE(),0,NULL
UNION ALL
SELECT 5,'YY',GETDATE(),11,''
GO

--	创建另一个表(或 实体表),稍后用于关联更新
SELECT * INTO #TestTab FROM [VTestTab] 

--	当前表、视图、临时表
SELECT [id],[name],[insertDate],[value],[info] FROM [dbo].[TestTab]
SELECT * FROM [dbo].[VTestTab]
SELECT * FROM [dbo].#TestTab

现在对单个视图更新:

--	对视图更新
UPDATE [VTestTab] SET [value]=100 WHERE id = 1

对单个视图更新,结果正常,但是执行了2次!~执行计划可以看到!~

是不是真的执行了两次?!

官方说明为:

如果为视图定义的 INSTEAD OF 触发器对视图执行了一条通常会再次触发 INSTEAD OF 触发器的语句,该语句不会被递归调用,而是将该语句解析为对视图所依存的基本表进行的修改,再次触发的操作就像该视图没有 INSTEAD OF 触发器一样。由 UPDATE 更改的列必须解析到一个基表。对基表的每次修改都将应用约束并触发为该表定义的 AFTER 触发器。

为了查看这两次执行到底哪次是有用的,现在使用 UPDATE(尝试更新) 和 COLUMNS_UPDATED (实际更新)来跟踪查看。

创建2张表,跟踪列更改情况。尝试更新表(attemptOperation)和实际更新表(factOperation):

--	DROP TABLE attemptOperation,factOperation
CREATE TABLE attemptOperation(Col VARCHAR(20),isUpdate BIT)
CREATE TABLE factOperation(Col VARCHAR(20),isUpdate BIT)

更改视图,将对列的更新情况记录到表中,但不触发更新:

ALTER TRIGGER [dbo].[tgr_VTestTab_update]
ON [dbo].[VTestTab] INSTEAD OF UPDATE
AS
BEGIN
IF(UPDATE([id]))
	INSERT INTO attemptOperation(Col,isUpdate) SELECT 'id',1
IF(UPDATE([name]))
	INSERT INTO attemptOperation(Col,isUpdate) SELECT 'name',1
IF(UPDATE([insertDate]))
	INSERT INTO attemptOperation(Col,isUpdate) SELECT 'insertDate',1
IF(UPDATE([value]))
	INSERT INTO attemptOperation(Col,isUpdate) SELECT 'value',1
IF(UPDATE([info]))
	INSERT INTO attemptOperation(Col,isUpdate) SELECT 'info',1

IF(SUBSTRING(COLUMNS_UPDATED(),1,1)&1=1)
	INSERT INTO factOperation(Col,isUpdate) SELECT 'id',1
IF(SUBSTRING(COLUMNS_UPDATED(),1,1)&2=2)
	INSERT INTO factOperation(Col,isUpdate) SELECT 'name',1
IF(SUBSTRING(COLUMNS_UPDATED(),1,1)&4=4)
	INSERT INTO factOperation(Col,isUpdate) SELECT 'insertDate',1
IF(SUBSTRING(COLUMNS_UPDATED(),1,1)&8=8)
	INSERT INTO factOperation(Col,isUpdate) SELECT 'value',1
IF(SUBSTRING(COLUMNS_UPDATED(),1,1)&16=16)
	INSERT INTO factOperation(Col,isUpdate) SELECT 'info',1
END
GO

再次对视图更新:

--	再对视图更新
UPDATE [VTestTab] SET [value]=10 WHERE id = 1

从执行计划看,视图是有更新了!,查看跟踪的表,也尝试更新并实际更新了!!~

再查看表中的记录,值却没有变化!~更新视图却没有反应到具体的表中!~

SELECT *
FROM [VTestTab] WHEREid= 1

值没有变化是正常的,因为触发器里没有定义更新的语句,但是从跟踪来看,视图的确是有更改的!~

现在改回原来的触发器:

ALTER TRIGGER [dbo].[tgr_VTestTab_update]
ON [dbo].[VTestTab] INSTEAD OF UPDATE
AS
UPDATE [VTestTab] SET
[name] = T2.[name],
[insertDate] = T2.[insertDate],
[value] = T2.[value],
[info] = T2.[info]
FROM [VTestTab] AS t1, inserted AS t2 WHERE t1.id = t2.id
GO
--	再对视图更新
UPDATE [VTestTab] SET [value]=100 WHERE id = 1

发现是有两个执行计划,从前面可以知道,第一个执行计划是没有实际更新的,它更新的是视图,没反馈到表中,只有第二次在触发器内部定义的更新操作才有用!~

那么问题就来了:为什么视图的更改没有反应在具体表中??难道微软所说的约束是在视图和表之间阻止了?(待解决)

 现在开始说下最初遇到的错误吧!~

--	关联更新,有错误!~
UPDATE t1 SET  t1.[value]=t2.[value]
FROM [VTestTab] t1 join #TestTab t2 on t1.id=t2.id

消息 414,级别 16,状态 1,第 1 行

不允许使用 UPDATE,因为该语句会更新视图 "VTestTab",而该视图参与联接并且有一个 INSTEAD OF UPDATE 触发器。

上面直接更新单个视图是正常的,唯有连接其他表更新时,才出现错误。

听说这个问题的出现,从07年已经提出了吧,只是一直没有被修复,不清楚 SqlServer 2014 修复了没有!~

有两种解决方法,但是性能不是很好!

--【方法一】
UPDATE [VTestTab] SET
 [value] = (SELECT [value] FROM #TestTab T WHERE [VTestTab].id=T.id) ,
 [info] = (SELECT [info] FROM #TestTab T WHERE [VTestTab].id=T.id)
WHERE EXISTS(SELECT * FROM #TestTab K WHERE [VTestTab].id=K.id)

--【方法二】
MERGE INTO [VTestTab] AS T1
USING #TestTab AS T2 ON  T1.id=T2.id
WHEN MATCHED THEN UPDATE
SET T1.[value] = T2.[value],T1.[info] = T2.[info];

参考:

设计 INSTEAD OF 触发器

通过视图修改数据

CREATE TRIGGER (Transact-SQL)

深入理解SQL Server 2005 中的 COLUMNS_UPDATED函数

时间: 2025-01-13 07:53:59

SqlServer INSTEAD OF UPDATE 视图触发器问题的相关文章

Mysql之视图 触发器 事务 存储过程 函数

视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,可以将该结果集当做表来使用. 使用视图我们可以把查询过程中的临时表摘出来,用视图去实现,这样以后再想操作该临时表的数据时就无需重写复杂的sql了,直接去视图中查找即可,但视图有明显地效率问题,并且视图是存放在数据库中的,如果我们程序中使用的sql过分依赖数据库中的视图,即强耦合,那就意味着扩展sql极为不便,因此并不推荐使用 #两张有关系的表 mysql> se

视图 触发器 存储过程 函数 流程过程 索引 慢查询

视图 触发器 存储过程 函数 流程过程 索引 慢查询 视图 触发器 事务 存储过程 内置函数 流程控制 索引 视图 1.什么是视图 ? 视图就是通过查询得到一张虚拟表,然后保存下来,下次直接使用即可 2.为什么要用视图 ? 如果要频繁使用一张虚拟表,可以不用重复查询 3.如何用视图 create view teacher2course as select * from teacher inner join course on teacher.tid = course.teacher_id; 强调

SQLServer中使用索引视图(物化视图)

物化视图:以前用的普通的视图,普通视图就是一段逻辑语句,对性能没有任何的提升,也不能创建索引,而物化视图会把视图里查询出来的数据在数据库上建立快照,它和物理表一样,可以创建 索引,主键约束等等,性能会有质的提升,但是其有缺点,会占用,可以设置它定时自动更新一次,也可以手动更新,当然也是可以设置及时更新的,但是会拉慢基表的增删改查操作,在这里我只讲思路,具体的话大家可以自己去研究. + ? --创建物化视图,每天晚上22:00:00自动更新 create materialized view VM_

SQL视图&触发器

SQL视图 在 SQL 中,视图是基于 SQL 语句的结果集的可视化的表. 视图包含行和列,就像一个真实的表.视图中的字段就是来自一个或多个数据库中的真实的表中的字段.我们可以向视图添加 SQL 函数.WHERE 以及 JOIN 语句,我们也可以提交数据,就像这些来自于某个单一的表. SQL CREATE VIEW 语法 CREATE VIEW view_name AS SELECT column_name(s) FROM table_name WHERE condition 视图的优点:1,可

sqlserver 登录记录(登录触发器)

本人自用 sqlserver  账号登录的记录(记录表+登录触发器) --存储账号的登录记录信息 use [YWmonitor] go create table access_log ( [code] [int] IDENTITY(1,1) NOT NULL, [session_id] [int] NULL, [login_time] datetime NULL, [host_name] [varchar](50) NULL, [original_login_name] [varchar](50

存储过程 视图 触发器 序列

一.存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它.存储过程的优点: 1.存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般SQL语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度.2.当对数据库进行复杂操作时(如对多个表进行Update,Insert,Query,Delete时),可将此复杂操作用存储过程封装起

SQL Service 数据库 基本操作 视图 触发器 游标 存储过程

use NewTest1 ---声明视图--- create view NewViewte as select StudentInfo.name as 姓名,StudentInfo.sex as 性别,course.name as 课程 from StudentInfo join Course on StudentInfo.Cid= Course.id ---调用视图 select * from NewViewte --- 声明触发器--- create trigger NewTri on Cl

Mysql学习---视图/触发器/存储过程/函数/索引 180101

视图 视图: 视图是一个虚拟表(非真实存在),动态获取数据,仅仅能做查询操作 本质:[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,并可以将其当作表来使用.由于视图是虚拟表,所以无法使用其对真实表进行创建.更新和删除操作,PyMysql是支持视图的. 仅能做查询用. 创建视图: create VIEW stu as select * from student; # 这里只是建立了一个对应关系,视图是虚表,动态获取数据 select * from stu;

数据库 --- 40 视图 触发器 存储过程 事务 函数

一.视图  (view) 视图是一种虚拟表,可以把查询出来的临时表保存下来 1.创建视图 2.删除视图 3.修改视图,(原始表的记录也跟着修改) 4.查看视图 二.  触发器(trigger)可进行  增删改  操作 1.创建触发器 2.删除触发器 实例: 三.存储过程  1. 优点: 缺点: 2.程序与数据库结合使用的三种方式 3.创建简单存储过程(无参) 4.创建存储过程(有参) ① in  传入参数 ② out  返回值 #查看存储过程的一些信息:show create procedure