mssql表多外键级联

时间:2012-09-10 12:58:49

标签: sql-server foreign-keys cascade

我确信这是可能的,但对于我的生活,我无法理解。

我创建的是一个用户历史记录MSSQL表,用于保存对用户和由谁进行的更改。此表包含两个引用我的另一个表(User)的外键 - 一个用于受影响用户的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”字段中的值不会更新以匹配User表中的引用值。

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

2 个答案:

答案 0 :(得分: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

所以更新级联。


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

您希望获得级联更新。这意味着TabA中的ID值将更改为当前不存在的新值(请注意 - 我们排除2n行交换其标识值的情况) - 否则,主键约束将被这次更新打破。

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

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

答案 1 :(得分:1)

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

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