我正在使用MSSQL 2008 R2。它具有可更新视图的便利功能。例如,如果我有一个表t将id映射到name:
create table t (id int not null primary key, name varchar(100) not null unique)
然后另一个表格提供了一些ID和更多信息:
create table u (id int not null primary key references t, info varchar(100) not null)
为方便起见,我可以看到一个视图,让我看到来自u的行,并使用名称列进行扩充:
create view v as select u.*, t.name from u u join t t on u.id = t.id
然后我现在可以按名称进行更新,而不是id:
update v set info = 'foo' where name = 'fred'
最方便。但是,如果我想删除'fred'的行,会发生什么?
delete v where name = 'fred' -- Fails
我收到错误
视图或函数'v'不可更新,因为修改会影响多个基表。
正如SQL Updatable View with joined tables(指的是Oracle,但MSSQL的情况看起来相同)中所述,只要只有一个键,就可以在多个基表上拥有可更新的视图。 -preserved 表;粗略地说,这是表中任何一行在视图中最多出现一次的位置。在上面的视图中,我们可以看到t和u都是密钥保留的表。但我们可以通过调整视图定义来作弊:
create view v as
select u.*, (select t.name from t t where t.id = u.id) as name
from u u
这给出了与以前相同的行,但现在允许更新:
update v set info = 'foo' where name = 'fred'
从语义上讲,来自t的任何行在视图中最多出现一次仍然是正确的,但是因为我们没有以正常方式加入t,所以我们没有达到更新限制。此外,我们也可以从这个视图中删除:
delete from v where name = 'fred'
这是正确的,从基础表中删除u而不是从t中删除。很明显,由于早期的视图表示为简单连接,因此无法判断“删除”操作是应该从u还是从t(或两者)中删除行。
对于许多'select'查询,使用重写视图的执行计划有点不同,所以我可能期望它在某些情况下执行速度稍慢。很遗憾,优化器无法看到(在这种特殊情况下,存在唯一索引)两个视图具有相同的数据。
您还可以使用函数创建可更新视图:
create function dbo.get_name(@id int) returns varchar(100) as begin
declare @r varchar(100)
select @r = name from t where id = @id
return @r
end
create view v as select *, dbo.get_name(id) as name from u
这可以提供不同的(通常更复杂的)查询计划,因此它可能会更慢。
因此,我们有两种可能的方法来制作可更新的视图,但它们并不完全令人满意。让更新和删除操作正常工作会很好,但是要确保视图在选择查询上的性能不会比两个表的简单连接差。也许你可以给查询引擎一些提示。有人可以建议吗?
答案 0 :(得分:2)
(select t.name from t t where t.id = u.id)
是一个非常Macgyver的技巧,你只需要一个表来限制删除表。
我建议的一个解决方案是使用而不是触发器,这将允许您个性化删除语句将对视图执行的操作。
自定义触发器可能不会影响视图的自动优化。
有些网站通过一些例子对它进行了更深入的了解: http://blogs.msdn.com/b/anthonybloesch/archive/2009/02/16/insteadoftriggerspart1.aspx 和 http://www.mssqltips.com/sqlservertip/1804/using-instead-of-triggers-in-sql-server-for-dml-operations/