2010-09-08 106 views
8

我正在处理一个正在扩展以在多租户配置中运行的旧应用程序。基本体系结构采用旧应用程序,并为每个表添加一个StoreID列。然后,每个租户通过一组的观点,即在商店ID过滤器,像看到了遗留表:如何编写INSTEAD OF INSERT触发器,为任何表设置一列?

create view AcmeBatWings.data as 
select * from dbo.data d where d.StoreId = 99 

它比票友了一点,但这种简化的问题。现在

,我可以创造这样的

create trigger tr_Tenant_fluff on AcmeBatWings 
instead of insert 
as 
insert into AcmeBatWings (Name, StoreId) 
select i.Name, 99 from inserted i 

触发假设有名称和STOREID列一个简单的表。

我的问题是,我有100多个表,如果我要按照这种模式,我将不得不为每个表列出专门的触发器,列出每个表的所有字段。不仅在短期内令人厌烦的是维护噩梦,因为任何表更改都需要包含触发器修改。

那么,如何写一个触发器,只要在每次插入或更新时说,就可以将StoreId字段设置为99,以使用StoreId的任何表?

感谢您帮助SQL新手出来!

+0

你提到该系统是多租户的;你打算为所有的触发器硬编码为'99'还是StoreId取决于谁在使用系统?另外,什么版本的SQL Server? – LittleBobbyTables 2010-09-08 20:59:52

+0

你是说你对每个参照相同基表的不同意见,唯一的区别是他们有不同的id过滤器? – 2010-09-08 20:59:57

+0

您的示例中的视图和触发器未对齐。在视图中,AcmeBatWings是一个模式名称。在触发器中,它是一个对象名称。这是什么?我假设一个模式名称。它有很大的不同。 – 2010-09-09 01:45:59

回答

7

所以看起来你正在使用多个模式来传达信息存储,同时保持对象名称一致,每家商店瓦特/一个模式,是吗?以及某种连接/用户魔法,以便查询达到正确的视图。

如果是这样,我提出了两个令人震惊的黑客和一个推荐的解决方案(所以你知道你的选择)。

程度严重劈#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 

的劈#2的好处超过砍#1,你可以使用SELECT *定义商店视图,如果基表发生更改,则只需使用sp_refreshview重新编译所有商店视图。缺点是您要将插入的数据从一个中间表复制到另一个中,并更新第二个表。这已经使您的INSTEAD OF INSERT触发器的开销增加了三倍,这已经相当昂贵。即,INSTEAD OF INSERT触发的

  • 基开销 - >成本来填充inserted - >x
  • 成本人口#insertedinserted→约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'[email protected] 
+ N'INSTEAD OF INSERT'[email protected] 
+ N'AS BEGIN'[email protected] 
+ 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' ')[email protected] 
+ N' )'[email protected] 
+ 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' ')[email protected] 
+ N' FROM inserted'[email protected] 
+ N'END'[email protected] 
+ N'GO'[email protected] 
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 
+0

Oooo,我喜欢脚本部分。我现在要睡觉了,但我会在早上仔细看看。 – Ukko 2010-09-09 02:43:42

+0

这是票!谢谢! – Ukko 2010-09-09 17:17:34

2

而不是使用触发器,为什么不通过使 StoreId NOT NULL更新每个表,并给它默认值99?

编辑基于澄清

你可以尝试的插入操作后,UPDATE触发器来替代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 

虽然这种更新您刚才插入或更新的数据,当您添加或删除表中的列时,它确实具有不会中断的好处。

+0

@Martin Smith讨论的第三种模型 - 这是我想知道的,但这个例子使用了一个硬编码的'99',所以这让我困惑。 – LittleBobbyTables 2010-09-08 20:58:44

+0

是的,我删除了该评论,因为我并不确定自己。 – 2010-09-08 21:00:55

+0

商店编号是不是绑定到视图,而不是基表?在这种情况下,默认基表中的列不会解决问题。 – 2010-09-09 01:40:17

2

所以,如果我有这个权利,每个商店都有自己的ID。数据库部署到每个商店,并且数据库应该使用最少的代码工作量基于其已部署的位置记录不同的StoreId。这是我的建议。在数据库中创建一个表来容纳StoreId。创建一个函数来检索StoreId。然后在每个表中创建StoreId列作为使用该函数的计算列。因此,在每个部署中,唯一的更改是将StoreId更新到一个表中。喜欢的东西:

/* 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 
+0

此解决方案的缺点: – 2015-12-07 13:58:38

0

只是绊了这个老问题:

您可以创建为STOREID默认约束(通过选择SYS.COLUMNS找到与STOREID列的所有表) ,对于固定的99或者在另一个表中查找的函数或者返回固定99的函数(这样当你将db移动到另一个存储时,你只需要改变一个函数而不是100个约束)

相关问题