3

我相信这是可能的,但对于我的生活,我无法弄清楚。

我创建的是一个用户历史 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”字段中的值将不会更新以匹配它们在用户表中的引用值。

我不知道如何实现这个目标。

4

2 回答 2

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)
)

它们比你的桌子简单,但希望足够接近。我们需要一个不可变的标识符 in TabA,显然IDs 不是,因为关键是要对它们进行级联更改。所以我添加了_RowID.

至少实现一个真正的外键并在此基础上模拟级联行为会很好,但一些简单的反射将证明总有一个点会破坏外键。所以我们模拟一下:

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:

您想要级联更新。这意味着其中的 ID 值TabA将更改为当前不存在的新值(警告 - 我们排除了 2n 行交换其标识值的情况) - 否则,主键约束将被此更新打破.

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

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

于 2012-09-10T14:24:34.463 回答
1

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

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

于 2012-09-10T13:20:34.640 回答