Pretty general question regarding triggers in SQL server 2005.
In what situations are table triggers fired and what situations aren't they?
Any code examples to demonstrate would be great.
I'm writing a audit based databases and just want to be aware of any situations that might not fire off the triggers that I have set up for update, delete and insert on my tables.
A example of what I mean,
UPDATE MyTable SET name = 'test rows' WHERE id in (1, 2, 3);
The following statement only fires the update trigger once.
When do you want them to fire?
CREATE TRIGGER AFTER ACTION
That runs after the action (insert update delete) being committed. INSTEAD OF fires the trigger in place of the action.
One of the biggest gotchas with triggers is that they fire whenever an action is performed, even if no rows are affected. This is not a bug, and it's something that can burn you pretty quickly if you aren't careful.
Also, with triggers, you'll be using the inserted and deleted tables. Updated rows are listed in both. This throws a lot of folks off, because they aren't used to thinking about an update as a delete then insert.
The MSDN documentation actually has a pretty in-depth discussion about when triggers fire and what effect they have here.
On 2008 you can use built in Change Data Capture
Also There are quite a few situations when triggers do not fire, such as:
· A table is dropped.
· A table is truncated.
· Settings for nested and/or recursive triggers prevent a trigger from firing.
· Data is bulk loaded, bypassing triggers.
The following statement only fires the update trigger once.
Any action type statement only fires the trigger once no matter how many rows are affected, triggers must be written to handle multiple row inserts/updates/deletes.
If your trigger depends on only one row at a time being in the inserted or deleted pseudotables, it will fail. And worse it will not fail with an error, it will simply not affect all the rows you want affected by whatever the trigger does. Do not fix this through a loop or a cursor in a trigger, change to set-based logic. A cursor in a trigger can bring your entire app to a screeching halt while a transaction of 500,000 records processes and locks up the table for hours.
Bulk inserts by pass triggers unless you specify to use them. Be aware of this because if you let them by pass the trigger you will need code to make sure whatever happens in the trigger also happens after the bulk insert. Or you need to call the bulk inserts with the FIRE_TRIGGERS option.
I thought I'd highlight from the link Eric posted a situation in which a trigger would not fire:
Although a TRUNCATE TABLE statement is in effect a DELETE, it cannot activate a trigger because the operation does not log individual row deletions. However, only those with permissions on a table to execute a TRUNCATE TABLE need be concerned about inadvertently circumventing a DELETE trigger with a TRUNCATE TABLE statement.
Related
I am trying to understand SQL Server's triggers. As an example, we have a scenario with a table that has multiple triggers for insert.
If we have an issue in the last trigger, throw an error, and call to rollback the transaction, do the previous triggers also rollback?
In this scenario, we are talking about DML triggers and they are sequential, not nested. We are also referring to two or three insert or update triggers. We are using trigger order, so we know which trigger in the series is the "last" trigger to execute.
My question is a little bit theoretical because I don't have any concrete working example. But I think it's worth to answer it.
What is the proper way to write insert-triggers in SQL Server?
Let's say I create a trigger like this (more or less pseudocode)
CREATE TRIGGER MY_TRIGGER
ON MY_TABLE
FOR INSERT AS
DECLARE #myVariable;
DECLARE InsertedRows CURSOR FAST_FORWARD FOR SELECT A_COLUMN FROM INSERTED;
OPEN InsertedRows;
FETCH NEXT FROM InsertedRows INTO #NewOrderCode;
...
INSERT INTO ANOTHER_TABLE (
CODE,
DATE_INSERTED
) VALUES (
#myVariable,
GETDATE()
);
...etc
Now what if someone else create another trigger on the same table and that trigger would change some columns on inserted rows? Something like this
CREATE TRIGGER ANOTHER_TRIGGER
ON MY_TABLE
FOR INSERT AS
UPDATE MY_TABLE
SET A_COLUMN = something
WHERE ID IN (SELECT ID FROM INSERTED);
...etc
Then my trigger (if fired after the another trigger) operates on wrong data, because INSERTED data are not the same as the real inserted data in the table which have been changed with the other trigger right?
Summary:
Trigger A updates new inserted rows on table T, trigger B then operates on dirty data because the update from trigger A is not visible in the INSERTED pseudo table which trigger B operates on. BUT if the trigger B would operate directly on the table instead of on the pseudo table INSERTED, it would see updated data by trigger A.
Is that true? Should I always work with the data from the table itself and not from the INSERTED table?
I'd usually recommend against having multiple triggers. For just two, you can, if you want to, define what order you want them to run in. Once you have a few more though, you have no control over the order in which the non-first, non-last triggers run.
It also increasingly makes it difficult just to reason about what's happening during insert.
I'd instead recommend having a single trigger per-table, per-action, that accomplishes all tasks that should happen for that action. If you're concerned about the size of the code that results, that's usually an indication that you ought to be moving that code out of the trigger all together - triggers should be fast and light.
Instead, you should start thinking about having the trigger just record an action and then use e.g. service broker or a SQL Server job that picks up those records and performs additional processing. Importantly, it does that within its own transactions rather than delaying the original INSERT.
I would also caution against the current code you're showing in example 1. Rather than using a cursor and inserting rows one by one, consider writing an INSERT ... SELECT statement that references inserted directly and inserts all new rows into the other table.
One thing you should absolutely avoid in a trigger is using a CURSOR!
A trigger should be very nimble, small, fast - and a cursor is anything but! After all, it's being executed in the context of the transaction that caused it to fire. Don't delay completion of that transaction unnecessarily!
You need to also be aware that Inserted will contain multiple rows and write your trigger accordingly, but please use set-based techniques - not cursors and while loops - to keep your trigger quick and fast.
Don't do heavy lifting, time-consuming work in a trigger - just updating a few columns, or making an entry into another table - that's fine - NO heavy lifting! and no e-mail sending etc!
My Personal Guide to SQL Trigger Happiness
The trigger should be light and fast. Expensive triggers make for a slow database for EVERYBODY (and not incidentally unhappiness for everybody concerned including the trigger author)
One trigger operation table combo please. That is at most one insert trigger on the foo table. Though the same trigger for multiple operations on a table is not necessarily bad.
Don't forget that the inserted and deleted tables may contain more than a single row or even no rows at all. A happy trigger (and more importantly happy database users and administrators) will be well-behaved no matter how many rows are involved in the operation.
Do not Not NOT NOT ever use cursors in triggers. Server-side cursors are usually an abuse of good practice though there are rare circumstances where their use is justified. A trigger is NEVER one of them. Prefer instead a series of set-oriented DML statements to anything resembling a trigger.
Remember there are two classes of triggers - AFTER triggers and INSTEAD OF triggers. Consider this when writing a trigger.
Never overlook that triggers (AFTER or INSTEAD OF) begin execution with ##trancount one greater than the context where the statement that fired them runs at.
Prefer declarative referential integrity (DRI) over triggers as a means of keeping data in the database consistent. Some application integrity rules require triggers. But DRI has come a long way over the years and functions like row_number() make triggers less necessary.
Triggers are transactional. If you tried to do a circular update as you've described, it should result in a deadlock - the first update will block the second from completing.
While looking at this code though, you're trying to cursor through the INSERTED pseudo-table to do the inserts - nothing in the example requires that behaviour. If you just insert directly from the full INSERTED table you'd get a definite improvement, and also less firings of your second trigger.
I have the following trigger to save the changes to a log table. However, it will not catch the changes triggered the trigger? Or is there another solution?
alter trigger trigger_xxx on table1 after delete, update, insert
as
begin
declare #lastVersion bigint = coalesce((select max(SYS_CHANGE_VERSION) from [log]), 0)
insert into [log]
([SourceColumnDescriptionPattern], SYS_CHANGE_VERSION, SYS_CHANGE_OPERATION, SYS_CHANGE_COLUMNS, SYS_CHANGE_CONTEXT)
SELECT [SourceColumnDescriptionPattern], SYS_CHANGE_VERSION, SYS_CHANGE_OPERATION, SYS_CHANGE_COLUMNS, SYS_CHANGE_CONTEXT
FROM changetable(changes [table1], #lastVersion) as ct
end
Change Tracking is intended for synchronization purposes. For example you can use it to find out if a application side cache needs to be refreshed. Therefore you do not want for that information to show up before the transaction is committed. As your trigger executes within the transaction the changes are not visible yet.
Why are you trying to duplicate the information available in Change Tracking? Cant you just use those functions and DMVs instead of your own?
Assuming you have a good reason, your best bet is probably to use a trigger and capture the affected primary key together with other pertinent information like a timestamp yourself. However there is no real good way to enforce that trigger being executed before all others so you might still end up in the same situation. You could try sp_settriggerorder in your case: http://msdn.microsoft.com/en-us/library/ms186762.aspx It might be enough in your situation.
I'm using transactional replication with updatable subscription.
when I add a trigger for update in one of my tables which is chosen for replication in publisher, I encounter with this error:
Maximum stored procedure, function,trigger,
or view nesting level exceeded(limit 32)
My trigger code is
create trigger Isupdated
on tbl_worker
for update as
update tbl_worker SET
Isup=1
where id= (select id from inserted)
what's wrong?
It looks like you've written a recursive (aka nested) trigger.
Perhaps the trigger is causing an update on the table, causing the trigger to be fired again?
If you post the code, that would help us explain exactly what the issue is.
http://www.sqlmonster.com/Uwe/Forum.aspx/sql-server-programming/4752/Maximum-stored-procedure-function-trigger-or-view
The above link provides a solution to trigger nesting. This can be very helpful when you already have a trigger and then replication adds another one. I like this more than combining the triggers because it doesn't force you to mix code related to functionality and code related to replication.
To sum up the solution:
You can prevent the nested firing from happening by assigning the triggers
an order with sp_settriggerorder and add the following check to the
beginning of the trigger you set to fire first to prevent it being fired by
the other trigger:
CREATE TRIGGER....
IF TRIGGER_NESTLEVEL > 1 RETURN
In Microsoft SQL Server :
I've added an insert trigger my table ACCOUNTS that does an insert into table BLAH based upon the inserted values.
The inserts come from only one place, and those happen one at a time. (By that, I mean, that there's never two inserts in a transaction - two web users could, theoretically click submit and have their inserts done in a near-simulataneous way.)
Do I need to adapt the trigger to handle more than one row being in inserted, the special table created for triggers - or does each individual insert transaction launch the trigger separately?
Each insert calls the trigger. However, if a single insert adds more than one row the trigger is only called once, so your trigger has to be able to handle multiple records.
The granularity is at the INSERT statement level not at the transaction level.
So no, if you have two transactions inserting into the same table they will each call the trigger ATOMICALLY.
BOb
in your situation each insert happens in its own transaction and fires off the trigger individually, so you should be fine. if there was ever a circumstance where you had two inserts within the same transaction you would have to modify the trigger to do either a set based insert from the 'inserted' table or some kind of cursor if additional processing is necessary.
If you do only one insert in a transaction, I don't see any reason for more rows to be in inserted, except if there was a possibility of recursive trigger calls.
Still, it could cause you troubles if you'd change the behavior of your application in future and you forget to change the triggers. So just to be sure, I would rather implement the trigger as if it could contain multiple rows in inserted.