2012-09-10 123 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