• SSMS and the related "Edit" GUI is nothing more than an application.  In this case, it's like many other applications where it displays some data, allows you to make changes, and then displays those changes.  It displays those changes by reading a result set.  And that's the problem with your trigger code.  While it works just fine if you do a manual update, it returns an unexpected result set because you forgot to include something to suppress that unexpected result set... SET NOCOUNT ON.

    Add that right after the BEGIN in your trigger and you'll be all set.  Let me rephrase that... the trigger will work as you have designed it to work and THAT is a huge problem.

    To make a very long story short, if you look at any of the auditing methods that SQL Server contains natively, NONE OF THEM AUDIT INSERTs and there's a very good reason for that.  Auditing INSERTs produces instant and unnecessary duplication of data.  Consider the following...

    You have an "original" table and a related "audit" table...

    If you do an insert into the original table, where does the original data live?  The answer is, of course, in the original table.  If you've made the mistake of auditing inserts, then you've duplicated that data in your audit table.  If you never make another change to that particular row, the original data persists in both the original table and in the audit table.  Instant and permanent duplication data.  Your 500 GB table now requires 1 Terabyte of disk space, which doubles backup times, doubles restore times, doubles backup to tape times, doubles restore from tape times, doubles any index maintenance you may have, etc, etc.

    Now... look at your trigger.  Not only are you making the mistake of auditing inserts, but you're duplicating even more data by auditing both the INSERTED and DELETED logical tables.  For any given row, you're saving both the previous condition and the new condition of the row during an UPDATE.  So, after you've stored a duplicate of the data during an INSERT (1st dup), you do an UPDATE, which saves another copy of the original insert and saves a copy of the current row (INSERTED), the latter of which can also be found in the original table.  So, with just 1 update, you end up with 3 full rows in the audit table.  Now your 500GB table needs 1.5 TB of storage if each row that you insert suffers just one update each.  Each and every update will cause yet another duplicate row because of the INSERTED logical table.

    I recommend that you audit only the DELETED logical table with the understanding that the latest version of any given row will always be in the original table and that the "old" rows will be in the audit table.

    Bottom line... only store old values which are in the DELETED logic table.  You DBA, SAN Admin, and Backup Admin will love you lot's more than they will with your current trigger.

    Also remember that LOBs are NOT directly auditable through the logical tables and, if that requirement exists, you'll need to make some serious changes to your trigger code.

    Last but not least, take some pride in your code.  Add a correctly annotated "flower box" to both the stored procedure and resulting trigger code, take some time to standardize capitalization and indentation, and add some bloody comments to help people troubleshoot the code in the future.  I don't care if this is POP code (Proof-of-Principle) because if the code ends up working, you or someone else will find some excuse to not do those things and you'll end up with crappy looking and difficult to maintain code in production.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)