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.
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.
I have a problem with a SQL trigger. It is an "AFTER INSERT" trigger. It works for every IF EXISTS block, except for the one with the update and raise error combinations as shown below. It either updates and moves on or stops and doesn't update.
Code: (where I left off - multiple different attempts at it and they all failed)
IF EXISTS ( SELECT
[RPS].[Slip]
FROM
[DC].[dbo].[Slips] AS [RPS]
WHERE
[RPS].[Slip] = #ps
AND [RPS].[Status] = 0 )
BEGIN
BEGIN TRAN;
UPDATE
[DC].[dbo].[Slips]
SET
[Slip].[Status] = 1
FROM
[DC].[dbo].[Slips]
WHERE
[Slips].[Slip] = #ps;
SET #msg = ' ' + #NewLine + 'Inv. Decremented - Rollback' + #NewLine + 'Contact HD.' + #NewLine;
RAISERROR (#msg,16,1);
COMMIT TRAN;
RETURN;
END;
The goal is to update the table to a status of 1 when the IF EXISTS is caught and fire the RAISERROR. The RAISERROR is picked up by the java code and stops the processing from occurring. If I take the update out, the trigger raises the error and stops. If I take the raise error out, the trigger updates but moves on - and I don't want that... I want my cake and eat it too!
Thoughts?
In order to prevent the update to the underlying triggered table while allowing the update to the Slips table you will need an INSTEAD OF INSERT trigger, a VIEW, and an ownership chain to prevent direct modification of the underlying table by users or their application code. You cannot get there using a traditional AFTER UPDATE trigger for the reasons mentioned previously in the comments namely because both the triggering insert and the triggered update are wrapped in the same transaction, they must either both take effect or have neither take effect. You cannot commit only part of a transaction and a nested transaction is part of the outermost transaction it is contained within.
Now for the solution...
Part 1 - The View
Create a view with the same column structure and naming as the table that has the insert trigger that is giving you all this trouble. Something of the form:
CREATE VIEW [same-name-as-the-table-you-are-using]
AS
SELECT <list-all-columns-explicitly-please-dont-use-star>
FROM <original-table-with-slightly-different-name-now>
If the original table is named XXX then rename it something like XXX_SINK and use XXX for the view. The users and application developers should consider this view the "table" they are using.
Part 2 - The Ownership Chain
An ownership chain is established in SQL Server when a referencing object and a referenced object have the same owner. When a different party (not the owner) accesses the referencing object (in this case the view) the permissions for that party are evaluated against the referencing object as usual but are not evaluated against the referenced object (in this case the table). This has been a feature of SQL Server since its beginning but is not known or well-understood by a number of SQL developers. You can get more information about ownership chains here.
You will want to deny permissions to the user base to the table and grant them to the view. This means users can only insert or update rows through the view and not directly into the table. This is important because you don't want them bypassing what you will do in the next part...
Part 3 - The INSTEAD OF Trigger
Create an INSTEAD OF trigger on the view. The syntax is just like that of an AFTER trigger except the words INSTEAD OF appears in place of AFTER and that no insert operation has occurred yet at the point it is fired and unless the trigger itself effects an update on the "sink" table, no update will be performed at all. This trigger can mix and match whatever it wishes. Like the AFTER trigger there is an implied transaction but only explicit data modification operations in the trigger itself will be performed.
Remember that the trigger has to explicitly perform the insert into the underlying sink table. The rows to be inserted can be retrieved from the inserted special table just like in the case of the AFTER trigger. Remember that there may (as far as SQL Server is concerned at least) be more than one row to be inserted (in fact there can be zero rows in the case of a zero-row insert statement). You will need to decide whether or not to allow the good rows or deny insertion of all the rows. Given your requirements, I suspect the later.
As a matter of sound database design, I would strongly recommend against -
The trigger restricting inserts to single rows, and
Using any form of cursor inside the trigger to deal with multi-row inserts. Use set-oriented DML instead.
Even though the current application may only insert rows one at a time, the database should not impose such a restriction.
A reasonable RAISERROR (that is one with sensible severity and state values) will not cause anything to be aborted or rolled back.
Doing these things in this combination should produce the result you desire.
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.
In SQL Server 2008, I have a scenario where I have a table with complex validation upon insert / update. This includes needing to temp convert an XML input into a table in order to validate its data against a permanent table.
However, I also have the scenario where I will often update simple integer columns that require no validation. From what I have read here, it seems that SQL Server is going to return the entire row in the temp in-memory "inserted" table, not just the affected columns, when I perform an update. If this is so, then it means for every simply integer update I perform, complex XML validation will be needlessly done.
Do I understand this correctly and if so, how do I get around this short of requiring inserts / updates via a stored proc?
Yes, first off, the triggers will fire for EVERY INSERT or UPDATE operation - you cannot limit that to only fire when certain columns will be affected. You can check inside the trigger to see whether or not certain columns have been affected and make decisions based on that - but you cannot prevent the trigger from firing in the first place.
And secondly, yes, when the trigger fires, you will have ALL columns of the underlying table in the INSERTED and/or DELETED pseudo tables.
One way you might want to change this is by moving the large XML column into a separate table and put that big heavy XML validation trigger only on that table. In that case, if you update or insert into the base table only, you'll get less data, less validation logic. Only when you insert or update into the XML table, you'll get the whole big validation going.
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.