Tell me more ×
Database Administrators Stack Exchange is a question and answer site for database professionals who wish to improve their database skills and learn from others in the community. It's 100% free, no registration required.

I've got an UPDATE trigger on a table that watches for a specific column changing from one specific value to any other value. When this happens, it updates some related data in another table via a single UPDATE statement.

The first thing the trigger does is check to see if any updated rows had the value of this column changed from the value in question. It simply joins INSERTED to DELETED and compares the value in that column. If nothing qualifies, it bails out early so the UPDATE statement doesn't run.

IF NOT EXISTS (
    SELECT TOP 1 i.CUSTNMBR
    FROM INSERTED i
        INNER JOIN DELETED d
            ON i.CUSTNMBR = d.CUSTNMBR
    WHERE d.CUSTCLAS = 'Misc'
        AND i.CUSTCLAS != 'Misc'
)
    RETURN

In this case, CUSTNMBR is the primary key of the underlying table. If I do a large update on this table (say, 5000+ rows), this statement takes AGES, even if I haven't touched the CUSTCLAS column. I can watch it stall on this statement for several minutes in Profiler.

The execution plan is bizarre. It shows an Inserted Scan with 3,714 executions, and ~18.5 million output rows. That runs through a filter on the CUSTCLAS column. It joins this (via nested loop) to a Deleted Scan (also filtered on CUSTCLAS), which executes only once and has 5000 output rows.

What idiotic thing am I doing here to cause this? Note that the trigger absolutely must properly handle multi-row updates.

EDIT:

I also tried writing it like this (in case EXISTS was doing something unpleasant), but it's still just as terrible.

DECLARE @CUSTNMBR varchar(31)
SELECT TOP 1 @CUSTNMBR = i.CUSTNMBR
FROM INSERTED i
    INNER JOIN DELETED d
        ON i.CUSTNMBR = d.CUSTNMBR
WHERE d.CUSTCLAS = 'Misc'
    AND i.CUSTCLAS != 'Misc'

IF @CUSTNMBR IS NULL
    RETURN
share|improve this question
Can you get rid of the "TOP 1"? I would think that is causing some overhead that may not be required if you are just checking to see if there is a single case... – JHFB Mar 28 '12 at 18:06

2 Answers

up vote 3 down vote accepted

You could evaluate using explicit INNER MERGE JOIN or INNER HASH JOIN hints but given that you are presumably using these tables again later in the trigger you are probably better off just inserting the contents of inserted and deleted tables into indexed #temp tables and being done with it.

They do not get useful indexes created for them automatically.

share|improve this answer
But 5000 rows wouldn't use an index anyway most likely. – HLGEM Mar 28 '12 at 18:38
@HLGEM - It would on the inside of a nested loops join as opposed to doing 5,000 table scans. Though maybe cardinality estimates are inaccurate as no column stats to work off and once in #temp table it won't use nested loops anyway... – Martin Smith Mar 28 '12 at 18:42
Okay, this speeds it up tremendously, however there's the potential for cascading trigger execution. If I use the same temp table names (#i, #d) in each trigger, they conflict. Is there a better/safer solution than just using a different temp table name in every trigger? – db2 Mar 28 '12 at 19:16
Could evaluate using table variables (with a primary key defined on CUSTNMBR to create the unique clustered index) and use the OPTION (RECOMPILE) hint to get it to take account of number of rows or maybe just use a particular naming convention such as #i_dbo_YourTable – Martin Smith Mar 28 '12 at 19:23
I think I'll settle for naming them like #trigger_name_i. If I go with table variables, I'll have to clutter up the code even more with explicit CREATE TABLEs. We've got cascading triggers, but not recursive triggers, so I think I'll be safe... – db2 Mar 28 '12 at 19:43

I might try to rewrite using if exists

IF EXISTS (SELECT TOP 1 i.CUSTNMBR     
            FROM INSERTED i         
            INNER JOIN DELETED d             
            ON i.CUSTNMBR = d.CUSTNMBR and d.custclass = 'Misc'  
            WHERE d.CUSTCLAS <>i.CUSTCLAS)    
BEGIN

--do your triggerstuff here
END
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.