2012-09-10 84 views
3

我相信这是可能的,但对于我的生活我无法弄清楚。mssql表多外键级联

我创建的是一个用户历史MSSQL表,用于保存对用户和由谁进行的更改。该表包含两个引用我的另一个表(用户)的外键 - 一个用于受影响用户的fkey和另一个用于进行更改的用户的fkey。

我需要的是对(用户)表进行任何更改以级联和更新此新表中的相应条目。

在新表(User_History)的字段如下(每个用户由两个字段识别的):

Affected_User_House_Id - int 
Affected_User_Id - int 
Modified_By_User_House_Id - int 
Modified_By_User_Id – int 
Modification_Date - datetime 
ModificationMade - ntext 

每个字段是除了“ModificationMade”主键。字段“Modification_Date”精确到1秒。 我遇到的问题是创建所述级联。 我试图运行下面的T-SQL代码:

ALTER TABLE [User_History] WITH CHECK 
ADD CONSTRAINT [FK_User_History_User] FOREIGN KEY([Affected_User_House_Id], [Affected_User_Id]) 
REFERENCES [User] ([User_House_Id], [User_ID]) 
ON UPDATE CASCADE 
GO 

ALTER TABLE [User_History] CHECK CONSTRAINT [FK_User_History_User] 
GO 

ALTER TABLE [User_History] WITH CHECK 
ADD CONSTRAINT [FK_User_History_User_ModifiedBy] FOREIGN KEY([Modified_By_User_House_Id], [Modified_By_User_Id]) 
REFERENCES [User] ([User_House_Id], [User_ID]) 
ON UPDATE CASCADE 
GO 

ALTER TABLE [User_History] CHECK CONSTRAINT [FK_User_History_User_ModifiedBy] 
GO 

这件T-SQL给了我以下错误:

*'User' table saved successfully 
'User_History' table 
- Unable to create relationship 'FK_User_History_User_ModifiedBy'. 
Introducing FOREIGN KEY constraint 'FK_User_History_User_ModifiedBy' on table 'User_History' may  cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or  modify other FOREIGN KEY constraints. 
Could not create constraint. See previous errors.* 

代码工作,如果我删除第二个“ON UPDATE CASCADE”的但是这将意味着“Modified_By_User_House_Id”和“Modified_By_User_Id”字段中的值不会更新以匹配其在用户表中引用的值。

我对如何实现这一目标感到迷茫。

+0

您是否尝试过将ON DELETE NO ACTION'到第二个约束? – Kermit

+0

你需要选择一个强制执行另一个执行另一个强制执行,如果有的话。正如你所发现的那样,SQL Server不能强制执行级联。这是因为两个FK都指向同一父母,它只能从一个FK级联到父母。 – Beth

回答

2

您只能指定一个级联。这里试图用两个触发器模拟多个级联:

create table TabA (
    ID1 int not null, 
    ID2 int not null, 
    _RowID int IDENTITY(1,1) not null, 
    constraint PK_TabA PRIMARY KEY (ID1,ID2), 
    constraint UQ_TabA__RowID UNIQUE (_RowID) 
) 
go 
create table TabB (
    ID1a int not null, 
    ID2a int not null, 
    ID1b int not null, 
    ID2b int not null, 
    constraint PK_TabB PRIMARY KEY (ID1a,ID2a,ID1b,ID2b) 
) 

它们比表格简单,但希望足够接近。我们需要一个不可变的标识符TabA,显然ID不是它,因为整个过程就是将更改级联到它们。所以我添加了_RowID

实现至少一个真正的外键,只是模拟级联行为是很好的,但一些简单的反射将表明总是有一个点FK将被打破。因此,我们模拟它:

create trigger FK_TabB_TabA on TabB 
after insert,update 
as 
    set nocount on 
    if exists (
     select 
      * 
     from 
      inserted i 
       left join 
      TabA a 
       on 
        i.ID1a = a.ID1 and 
        i.ID2a = a.ID2 
       left join 
      TabA b 
       on 
        i.ID1b = b.ID1 and 
        i.ID2b = b.ID2 
     where 
      a._RowID is null or 
      b._RowID is null) 
    begin 
     declare @Error varchar(max) 
     set @Error = 'The INSERT statement conflicted with the Foreign Key constraint "FK_TabB_TabA". The conflict occurred in database "'+DB_NAME()+'", table "dbo.TabB".' 
     RAISERROR(@Error,16,0) 
     rollback 
    end 

然后级联更新:

create trigger FK_TabB_TabA_Cascade on TabA 
after update 
as 
    set nocount on 

    ;with Updates as (
     select 
      d.ID1 as OldID1, 
      d.ID2 as OldID2, 
      i.ID1 as NewID1, 
      i.ID2 as NewID2 
     from 
      inserted i 
       inner join 
      deleted d 
       on 
        i._RowID = d._RowID 
    ) 
    update b 
    set 
     ID1a = COALESCE(u1.NewID1,ID1a), 
     ID2a = COALESCE(u1.NewID2,ID2a), 
     ID1b = COALESCE(u2.NewID1,ID1b), 
     ID2b = COALESCE(u2.NewID2,ID2b) 
    from 
     TabB b 
      left join 
     Updates u1 
      on 
       b.ID1a = u1.OldID1 and 
       b.ID2a = u1.OldID2 
      left join 
     Updates u2 
      on 
       b.ID1b = u2.OldID1 and 
       b.ID2b = u2.OldID2 
    where 
     u1.OldID1 is not null or 
     u2.OldID1 is not null 
go 

一些简单的插入:

insert into TabA (ID1,ID2) 
values (1,1),(1,2),(2,1),(2,2) 
go 
insert into TabB (ID1a,ID2a,ID1b,ID2b) 
values (1,1,2,2) 

那么下面得到一个错误。不太像一个内置的FK违规,但足够接近:

insert into TabB (ID1a,ID2a,ID1b,ID2b) 
values (1,1,2,3) 
--Msg 50000, Level 16, State 0, Procedure FK_TabB_TabA, Line 28 
--The INSERT statement conflicted with the Foreign Key constraint "FK_TabB_TabA". The conflict occurred in database "Flange", table "dbo.TabB". 
--Msg 3609, Level 16, State 1, Line 1 
--The transaction ended in the trigger. The batch has been aborted. 

这是我们希望能够进行更新:

update TabA set ID2 = ID2 + 1 

我们查询FK表:

select * from TabB 

结果:

ID1a  ID2a  ID1b  ID2b 
----------- ----------- ----------- ----------- 
1   2   2   3 

因此更新级联。


为什么你不能使用真正的FKS:

你想拥有的级联更新。这意味着TabA中的ID值将更改为一个当前不存在的新值(警告 - 我们排除了2n行交换其身份值的情况) - 否则,主键约束将被破坏更新。

因此,我们知道新的键值不会存在。如果我们要使用INSTEAD OF触发器尝试级联更新(在父级之前更新子表),那么我们尝试更新到TabB的新值尚不存在。或者,如果我们试图使用AFTER触发器进行级联更新 - 那么我们为时已晚。 FK约束已经阻止了更新。

我想你可能实现INSTEAD OF触发器,插入新行的“重复”,更新了孩子,然后删除旧行。在这种情况下,我认为你可以有真正的FK。但我不想尝试在所有情况下编写该触发器(例如,在有三行被更新的情况下,两个交换它们的ID值,另一个创建一个新的ID)

+0

我真的很喜欢这个想法,它给了我我想要的一切,但我不知道它是否是不必要的。你说一个模拟级联行为的真正外键最终会破灭;你能举一个例子吗? – user1625159

+0

@ user1625159 - 我在底部添加了一个部分,解释了我从真实FK开始的最初想法。 –

+0

我现在明白了。我的想法是,触发器可以关闭FKey约束检查,同时级联更新。我想这不能用MSSQL完成? – user1625159

1

根据this知识库文章,当“一个表不能在DELETE或UPDATE语句启动的所有级联参照动作的列表中出现多次”时出现此错误消息。

由于您有两条路径来自同一个表,所以可能的解决方法可能涉及在父表上创建一个新的键并在该子上创建一个外键([Affected_User_House_Id], [Affected_User_Id], [Modified_By_User_House_Id], [Modified_By_User_Id])。但是,这可能会产生很多开销。作为最后的手段,您可以使用触发器来强制执行关系完整性。

+0

感谢你们,很高兴知道我为什么会遇到这样的问题。 – user1625159