Sql 如何编写为任何表设置一列的INSTEAD OF INSERT触发器?

Sql 如何编写为任何表设置一列的INSTEAD OF INSERT触发器?,sql,sql-server,Sql,Sql Server,我正在开发一个遗留应用程序,该应用程序正在扩展以在多租户配置中运行。基本架构采用旧的应用程序,并在每个表中添加一个StoreID列。然后,每个租户通过一组根据存储id筛选的视图查看遗留表,如: create view AcmeBatWings.data as select * from dbo.data d where d.StoreId = 99 /* This table is updated with the unique value for each individual store

我正在开发一个遗留应用程序,该应用程序正在扩展以在多租户配置中运行。基本架构采用旧的应用程序,并在每个表中添加一个
StoreID
列。然后,每个租户通过一组根据存储id筛选的视图查看遗留表,如:

create view AcmeBatWings.data as 
select * from dbo.data d where d.StoreId = 99
/* This table is updated with the unique value for each individual store */
create table MyStore (
    StoreId int
)

insert into MyStore
    (StoreId)
    values
    (99)        
go

/* This function will be used in the computed column of each table */
create function dbo.LookupStoreId()
returns int
as
begin
    return (select StoreId from MyStore)
end
go

create table AcmeBatWings (
    Name char(10),
    StoreId as dbo.LookupStoreId()
) 

insert into AcmeBatWings
    (Name)
    values
    ('abcde')

select Name, StoreId from AcmeBatWings
go

/* Clean up after demo */
drop table AcmeBatWings
drop table MyStore
drop function dbo.LookupStoreId
go
这有点像幻想,但这简化了问题

现在,我可以创建一个这样的触发器

create trigger tr_Tenant_fluff on AcmeBatWings
instead of insert
as
insert into AcmeBatWings (Name, StoreId)
select i.Name, 99 from inserted i
假设一个包含Name和StoreId列的简单表

我的问题是,我有100多个表,如果我要遵循这种模式,我必须为每个表创建一个专门的触发器,列出每个表的所有字段。这不仅在短期内令人讨厌,而且是维护的噩梦,因为任何表更改都需要包括触发器修改

那么,对于任何具有StoreId的表,如何编写一个只在每次插入或更新时显示的触发器将StoreId字段设置为99呢

感谢您帮助一位SQL新手

与其使用触发器,为什么不更新每个表,使
StoreId
不为NULL,并给它一个默认值99

根据澄清进行编辑

您可以尝试在插入后更新触发器,以替代INSTEAD OF触发器

create trigger tr_Tenant_fluff on AcmeBatWings
AFTER insert, update
as

-- You'll need to get @StoreID here somehow

update AcmeBatWings 
set StoreID = @StoreID
where [Name] IN (SELECT [Name] FROM inserted) -- update based on primary key
虽然这会更新您刚刚插入或更新的数据,但它的好处是在表中添加或删除列时不会中断。

因此,如果我没有弄错,每个存储区都有自己的ID。DB部署到每个存储区,DB应根据部署的位置记录不同的StoreId,代码工作量最小。以下是我的建议。在数据库中创建一个表来保存StoreId。创建一个函数来检索该StoreId。然后在每个表中创建StoreId列作为使用该函数的计算列。因此,在每次部署中,唯一的更改是更新一个表中的StoreId。比如:

create view AcmeBatWings.data as 
select * from dbo.data d where d.StoreId = 99
/* This table is updated with the unique value for each individual store */
create table MyStore (
    StoreId int
)

insert into MyStore
    (StoreId)
    values
    (99)        
go

/* This function will be used in the computed column of each table */
create function dbo.LookupStoreId()
returns int
as
begin
    return (select StoreId from MyStore)
end
go

create table AcmeBatWings (
    Name char(10),
    StoreId as dbo.LookupStoreId()
) 

insert into AcmeBatWings
    (Name)
    values
    ('abcde')

select Name, StoreId from AcmeBatWings
go

/* Clean up after demo */
drop table AcmeBatWings
drop table MyStore
drop function dbo.LookupStoreId
go

因此,您似乎正在使用多个模式来传递存储信息,同时保持对象名称的一致性,每个存储只有一个模式,是吗?以及某种连接/用户魔法,以便查询能够找到正确的视图

如果是这样的话,我将介绍两种令人震惊的破解方法和一种推荐的解决方案(这样您就知道了自己的选择)

异常hack#1假设存储视图包括除StoreId之外的所有基表列,与基表的顺序位置相同,没有其他列:

CREATE TRIGGER tr_Tenant_fluff ON AcmeBatWings.data
INSTEAD OF INSERT 
AS BEGIN 
  DECLARE @StoreId INT

  SELECT @StoreId = StoreId FROM dbo.StoreSchemas 
  WHERE StoreSchema = OBJECT_SCHEMA_NAME(@@PROCID)

  INSERT dbo.data SELECT *, @StoreId FROM inserted
END
如果向基表中添加列,则必须更新所有存储视图以包含该列,否则触发器将中断

异常黑客2假定与(1)相同,只是StoreId包含在商店视图中:

CREATE TRIGGER tr_Tenant_fluff ON AcmeBatWings.data
INSTEAD OF INSERT 
AS BEGIN 
  DECLARE @StoreId INT

  SELECT @StoreId = StoreId FROM dbo.StoreSchemas 
  WHERE StoreSchema = OBJECT_SCHEMA_NAME(@@PROCID)

  SELECT * INTO #inserted FROM inserted
  UPDATE #inserted SET StoreId = @StoreId

  INSERT dbo.data SELECT * FROM #inserted
END
hack#2优于hack#1的好处是,您可以使用
SELECT*
定义存储视图,如果基表发生更改,只需使用
sp#u refreshview
重新编译所有存储视图即可。缺点是将插入的数据从一个中间表复制到另一个中间表,并更新第二个表。这使
触发器的开销增加了三倍,而不是INSERT
触发器,这在一开始就相当昂贵。即

  • 而不是INSERT的基本开销
    触发器->填充
    INSERT的成本
    ->
    x
  • inserted
    ->关于
    x
    填充
    #inserted
    的成本
  • 更新
    #插入的
    ->关于
    x
  • 骇人听闻的黑客攻击的总开销#2:大约3
    x
否则,最好编写触发器脚本。这是一个相当直接的过程,一旦您熟悉了系统表,您就可以根据自己的需要调整触发器的生成。因此,您还应该编写存储视图的脚本

要开始,请执行以下操作:

CREATE TABLE dbo.data (Name VARCHAR(10), StoreId INT)
GO
CREATE SCHEMA StoreA
GO
CREATE SCHEMA StoreB
GO
CREATE SCHEMA StoreC
GO
CREATE VIEW StoreA.data AS SELECT Name FROM dbo.data WHERE StoreId = 1
GO
CREATE VIEW StoreB.data AS SELECT Name FROM dbo.data WHERE StoreId = 2
GO
CREATE VIEW StoreC.data AS SELECT Name FROM dbo.data WHERE StoreId = 3
GO
CREATE TABLE dbo.StoreSchemas (StoreSchema SYSNAME UNIQUE, StoreId INT PRIMARY KEY)
GO
INSERT dbo.StoreSchemas VALUES ('StoreA', 1), ('StoreB', 2), ('StoreC', 3)
GO

DECLARE @crlf NCHAR(2) = NCHAR(13)+NCHAR(10)
SELECT
  N'CREATE TRIGGER tr_Tenent_fluff ON '+schema_name(v.schema_id)+N'.data'+@crlf
+ N'INSTEAD OF INSERT'+@crlf
+ N'AS BEGIN'+@crlf
+ N'  INSERT dbo.data ('
+ STUFF((
    SELECT @crlf+N'  , '+name FROM sys.columns tc 
    WHERE tc.object_id = t.object_id
      AND (tc.name IN (SELECT name FROM sys.columns vc WHERE vc.object_id = v.object_id)
        OR tc.name = N'StoreId')
    ORDER BY tc.column_id
    FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)')
    ,5,1,N' ')+@crlf
+ N'  )'+@crlf
+ N'  SELECT'
+ STUFF((
    SELECT @crlf+N'  , '+name
      + CASE WHEN name = N'StoreId' THEN ' = '+(
          SELECT CONVERT(NVARCHAR,StoreId) FROM dbo.StoreSchemas s 
          WHERE s.StoreSchema = SCHEMA_NAME(v.schema_id)
          )
        ELSE '' END
    FROM sys.columns tc 
    WHERE tc.object_id = t.object_id
      AND (tc.name IN (SELECT name FROM sys.columns vc WHERE vc.object_id = v.object_id)
        OR tc.name = N'StoreId')
    ORDER BY tc.column_id
    FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)')
    ,5,1,N' ')+@crlf
+ N'  FROM inserted'+@crlf
+ N'END'+@crlf
+ N'GO'+@crlf
FROM sys.tables t 
JOIN sys.views v 
  ON t.name = v.name 
 AND t.schema_id = SCHEMA_ID('dbo') 
 AND v.schema_id <> t.schema_id
WHERE t.name = 'data'
GO
创建表dbo.data(名称VARCHAR(10),StoreId INT)
去
创建模式存储A
去
创建模式存储库B
去
创建模式存储库
去
创建视图StoreA.data作为从dbo.data中选择名称,其中StoreId=1
去
创建视图StoreB.data作为从dbo.data中选择名称,其中StoreId=2
去
创建视图StoreC.data作为从dbo.data中选择名称,其中StoreId=3
去
创建表dbo.StoreSchemas(StoreSchema SYSNAME UNIQUE,StoreId INT主键)
去
插入dbo.StoreSchemas值('StoreA',1),('StoreB',2),('StoreC',3)
去
声明@crlf NCHAR(2)=NCHAR(13)+NCHAR(10)
挑选
N'CREATE TRIGGER tr_Tenent_fluff ON'+schema_name(v.schema_id)+N'.data'+@crlf
+N'而不是插入'+@crlf
+N'AS BEGIN'+@crlf
+N'插入dbo.data('
+东西((
从sys.tc列中选择@crlf+N'、'+name
其中tc.object\u id=t.object\u id
和(tc.name IN(从sys.columns vc中选择名称,其中vc.object\u id=v.object\u id)
或tc.name=N'StoreId')
按tc.column\u id排序
对于XML路径(“”),键入.value(“”.,'NVARCHAR(MAX)“”)
,5,1,N'')+@crlf
+N')'+@crlf
+N‘选择’
+东西((
选择@crlf+N','+name
+当name=N'StoreId'然后'='时的大小写+(
从dbo.stores中选择CONVERT(NVARCHAR,StoreId)
其中s.StoreSchema=SCHEMA\u NAME(v.SCHEMA\u id)
)
“否则”结束
从sys.tc列
其中tc.object\u id=t.object\u id
和(tc.name IN(从sys.columns vc中选择名称,其中vc.object\u id=v.object\u id)
或tc.name=N'StoreId')
按tc.column\u id排序
对于XML路径(“”),键入.value(“”.,'NVARCHAR(MAX)“”)
,5,1,N'')+@crlf
+N'来自插入的'+@crlf
+N'END'+@crlf
+N'GO'+@crlf
从sys.t表
加入sys.views v
在t.name=v.name上
和t.schema_id=schema_id('dbo'))
和v.schema_id t.schema_id
其中t.name='data'
去

刚刚被这个老问题绊倒了:

您可以为StoreId创建一个默认约束(通过选择sys查找具有StoreId列的所有表)。