Foreign Key Row Creation in Triggers

已答复 Foreign Key Row Creation in Triggers

  • 2012年7月6日 8:49
     
     
    This is about a Data Warehouse application I am developing. I think that that the more I could relate tables via foreign keys, the better the database would be.
    My OLTP cannot be relied upon for such integrity, so I took the step of, if the foreign keys table row is missing, I just create it with default values in non-key fields. So if I am doing an insert, then I have all the columns available to me to check whether a foreign key table row exists, and if it doesn't, create it. I opted to do this with instead-of triggers and they looked like this:
    ALTER TRIGGER [OLTP].[Trigger_Sub Ledger Balances] on
    [OLTP].[Sub Ledger Balances]
    INSTEAD OF INSERT, UPDATE AS
    Begin
    IF (UPDATE([Company Number]) OR UPDATE([S/L Number]))
    Begin
    INSERT INTO OLTP.[Sub Ledger Master] ([Company Number], [Sub Account Code], [UTYP])
    Select DISTINCT [Company Number], [S/L Number], 'T'
    from inserted i
    where i.[Company Number] is not null and i.[S/L Number] is not null
     and not exists
    (Select 1 from OLTP.[Sub Ledger Master] f where
    f.[Company Number] = i.[Company Number] and f.[Sub Account Code] = i.[S/L Number])
    End
    ... then all of the other FK tables followed by the insert or update to the table that the trigger is over:
    IF NOT EXISTS (SELECT * FROM DELETED)
    Begin
    INSERT INTO OLTP.[Sub Ledger Balances](
    ,[Company Number]
    ,[S/L Number]
    ,[G/L Number]
    ,[Fiscal Year Of Budget]
    ,[Opening Balance]
    ... Select w,x,y,z from inserted

    It is important to note is that if [Sub Ledger Master] also has a trigger, then we are nesting triggers.
    This all worked very well while I was still developing the "full database load" part of the project, but when I got to the incremental data load, I chose to use cursor processing to handle adds, changes and deletes to a table. It still works OK. But it is slow and this is because of the insert into foreign key table having to occur for every row. It is four times faster without the foreign key table inserts and this is important.

    So I turned the trigger on its head and did the Insert, into the table that the trigger is over, first, in a Try block with the insert into foreign key table in the corresponding Catch block. But if the Try went wrong, it seems I have to use XACT_STATE() = -1 in the catch to determine this and do a rollback transaction and this is where the problems start.

    I have had a variety of errors and hung SSMS too and it would take too long and be too detailed to go into these.

    So the question is: has anyone got experience of doing something similar who could give me guidance on how to go about this.

    Thanks in advance,
    Frank

全部回复

  • 2012年7月6日 9:54
     
     已答复

    The problem is that in a trigger by default all errors doom the transactions. You can change this by adding this statement to your trigger:

    SET XACT_ABORT OFF

    Then not all errors will doom the transaction, only some errors. Thankfully, the errors you want recover from, FK errors are recoverable.

    However, I think you are better of using the MERGE statement for the incremental loads, rather than using a cursor. MERGE was added in SQL 2008 and permits you to perform INSERT, UPDATE and DELETE in a single statement.


    Erland Sommarskog, SQL Server MVP, esquel@sommarskog.se
  • 2012年7月6日 13:00
     
     

    Thanks Erland. I didn't think I could use SET XACT_ABORT OFF in a trigger, but it works.

    I added If XACT_STATE() = -1 ROLLBACK TRANSACTION as first statement in the catch block

    I have been trying all sorts of things and just getting nowhere. I wish I'd asked the question a couple of days ago. In case anyone else is interested, the working trigger looks like:

    SET XACT_ABORT OFF
    StartLabel:
    Begin Try
    IF NOT EXISTS (SELECT * FROM DELETED)
    insert statement
    ELSE
    update statement
    End Try
    Begin Catch
    If XACT_STATE() = -1 ROLLBACK TRANSACTION
    If ERROR_CODE() = the foreign key error (547)
    Begin
    insert into foreign key table ... for all foreign keys
    GoTo StartLabel
    End
    End Catch