SQL Trigger - After Insert - Update statement and RAISERROR not working - sql-server

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.

Related

What is the proper way to write insert triggers in SQL Server?

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.

Can we replace a DML trigger with a stored procedure

Not sure if this has been asked before cause while typing the title text the possible duplicate given suggestion's doesn't match.
One of my colleague asked if a DML trigger functioning can be replaced totally with a stored procedure(SP). Well sounds bit weird at first but it's possible cause trigger is also a special type of SP but not explicitly callable.
I mean say for example: a AFTER INSERT Trigger named trg_insert1 defined on tbl1 which does update some data in in tbl2 like below (taken a SQL Server Example but question is not specific to any DB)
create trigger trg_insert1
after insert on tbl1
foreach row
begin
update tbl2 set somedata = inserted.tbl1somedata
where id = inserted.tbl1id;
end
Now this trigger can be replaced with a SP like below (using transaction block);
create procedure usp_insertupdate (#name varchar(10), #data varchar(200))
as
begin
begin try
begin trans
insert into tbl1(name, data) values(#name, #data);
update tbl2 set somedata = #data
where id = scope_identity();
commit trans
end try
begin catch
if ##TRANCOUNT > 0
rollback trans
end catch
end
Which will work perfectly in almost all cases of DML trigger like after/before -> insert/delete/update. BUT I really couldn't answer/explain
what the difference then?
Is it a good practice to do so?
Is it not possible in all cases?
Am I being thinking it over complex.
Please let me know what you think.
[NOTE: Not a specific RDBMS related question though]
I'll try to answer in a very general sense (you specified this is not targeted to a specific implementation).
First of all, a trigger is written in the same data manipulation language that you would use for a stored procedure. So in terms of capabilities Trigger and Stored Procedure are the same.
But...
a trigger is guaranteed to be invoked every time you alter the data, no matter if you do that through a stored procedure, another trigger, or by manually executing a SQL statement.
In fact you can expect a trigger to always execute (for its triggering statement) unless you explicitly disable it.
A stored procedure, on the other hand it is guaranteed never to run by itself unless you explicitly run it.
This has an important consequence: triggers are better at ensuring consistency. If someone in a hurry removes a record in your live instance by typing:
Delete from tablex where uid="QWTY10311"
any bookkeeping action implemented as a trigger will be executed, while if the user forgets (or maliciously avoid) following this with
Execute SP_TABLEX_LOG("DELETE","QWTY10311")
your DB will just lose the data silently.
Triggers have two other important characteristics that can be duplicated with stored procedures only through extra (sometimes significantly more expensive) effort.
First of all they are executed record-by-record. So if you are deleting 1 million records the logging will be performed for each operation. Good luck calling the appropriate stored procedure with a 1 million rows cursor as a parameter, ESPECIALLY if you want to do that after a manual operation as in my example above.
Second advantage: Triggers have a special scope where they can reference pre- and post- change values for each field.
So if you are incrementing a table of prices by 10% and want to log what the previous value was, and which user performed the action at what time, you will have "old-value", "new-value", "user-id" and "timestamp" in scope for any kind of operation you may want to do.
Again, doing this by invoking a stored procedure means you have to save the values to pass them to the stored procedure when it runs.
So why bother with SP anyway? (this will answer, hopefully, your question about "best use case").
Stored Procedure are better when you need to create complex business logic which will be invoked by an application layer. So if you want to know, for example, how many hotel rooms are available between two given dates and with the extra requirement that pets are allowed, a trigger would not be a good idea.
Especially because a trigger will not return any result to an invoking process...
So anytime you need to get some result to the caller, be it a query, a calculation, or anything else that has OUTPUT parameters, a trigger is useless.
Triggers should be used to enforce consistency. If a header record should not be deleted unless it has no children in other tables, enforce this with a trigger, maybe. If you need to log whoever changes a value in a field, no matter how, use a trigger.
In all other cases, use a stored procedure (keep also in mind that triggers will impact the responsiveness of any data update, just like indexes).
Yes stored procedures can be used to replace DML triggers in this way, and whether it is a good practice or not depends on your needs.
The main difference is that a trigger will run its code every time it is fired. In your example, if a user does an ad-hoc INSERT to tbl1, a trigger will fire and tbl2 will get updated.
A stored procedure can only be used to enforce this rule if ad-hoc INSERTs are not allowed.

SQL database triggers thread safe?

I have been wondering about the semantics of concurrency with regards to SQL Server database triggers. I have a database trigger that runs after an update to a table. The trigger sets a 'dateModified' column to the current time. This allows me to always know that the most recent update to the table occurred at X time. Below is what it would look like:
ALTER TRIGGER base.TR_myTrigger ON base.myTable
AFTER INSERT, UPDATE AS
BEGIN
DECLARE #dateModified AS DATETIMEOFFSET
SET #dateModified = SYSDATETIMEOFFSET()
UPDATE base.myTable
Set dateModified = #dateModified
WHERE id in (SELECT DISTINCT(id) FROM inserted)
END
In my scenario, the table can be updated at any time by any number of threads. Is this trigger safe within a multi-threaded context? If thread A updates the table, and thread B reads from it immediately after, will B see a state of the table with A's updates but not trigger's updates too? Or do triggers protect the table from being read from until all actions + their triggers have been performed?
Any insight into what exactly is happening under the hood with SQL database triggers would be appreciated. Thanks!
If thread A updates the table, and thread B reads from it immediately after, will B see a state of the table with A's updates but not trigger's updates too? Or do triggers protect the table from being read from until all actions + their triggers have been performed?
What this boils down to is: are the underlying operation and the trigger operation treated as an atomic unit or are they separate? The answer is: atomic. That is, you'll never see the results of the insert or update (in your case) and not see the dateModified column being dealt with via the trigger. That is, they both commit in one transaction. From the documentation:
The trigger and the statement that fires it are treated as a single transaction...
Triggers do not have any special properties regarding concurrency. They run as if you had manually executed that code.
Your trigger is safe because all rows that you read and write have been X-locked already by the triggering DML.

Simultaneous Triggers in SQL Server

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.

Is there a way to disable a SQL Server trigger for just a particular scope of execution?

In SQL Server 2005, is there a way for a trigger to find out what object is responsible for firing the trigger? I would like to use this to disable the trigger for one stored procedure.
Is there any other way to disable the trigger only for the current transaction? I could use the following code, but if I'm not mistaken, it would affect concurrent transactions as well - which would be a bad thing.
DISABLE TRIGGER { [ schema_name . ] trigger_name [ ,...n ] | ALL } ON { object_name | DATABASE | ALL SERVER } [ ; ]
ENABLE TRIGGER { [ schema_name . ] trigger_name [ ,...n ] | ALL } ON { object_name | DATABASE | ALL SERVER } [ ; ]
If possible, I would like to avoid the technique of having a "NoTrigger" field in my table and doing a NoTrigger = null, because I would like to keep the table as small as possible.
The reason I would like to avoid the trigger is because it contains logic that is important for manual updates to the table, but my stored procedure will take care of this logic. Because this will be a highly used procedure, I want it to be fast.
Triggers impose additional overhead on the server because they initiate an implicit transaction. As soon as a trigger is executed, a new implicit transaction is started, and any data retrieval within a transaction will hold locks on affected tables.
From: http://searchsqlserver.techtarget.com/tip/1,289483,sid87_gci1170220,00.html#trigger
I just saw this article recently highlighted on the SQL Server Central newsletter and it appears to offer a way which you may find useful using the Context_Info on the connection:
http://www.mssqltips.com/tip.asp?tip=1591
EDIT by Terrapin:
The above link includes the following code:
USE AdventureWorks;
GO
-- creating the table in AdventureWorks database
IF OBJECT_ID('dbo.Table1') IS NOT NULL
DROP TABLE dbo.Table1
GO
CREATE TABLE dbo.Table1(ID INT)
GO
-- Creating a trigger
CREATE TRIGGER TR_Test ON dbo.Table1 FOR INSERT,UPDATE,DELETE
AS
DECLARE #Cinfo VARBINARY(128)
SELECT #Cinfo = Context_Info()
IF #Cinfo = 0x55555
RETURN
PRINT 'Trigger Executed'
-- Actual code goes here
-- For simplicity, I did not include any code
GO
If you want to prevent the trigger from being executed you can do the following:
SET Context_Info 0x55555
INSERT dbo.Table1 VALUES(100)
If your trigger is causing performance problems in your application, then the best approach is to remove all manual updates to the table, and require all updates to go through the insert/update stored procedures that contain the correct update logic. Then you may remove the trigger completely.
I suggest denying table update permissions if nothing else works.
This also solves the problem of duplicate code. Duplicating code in the update SP and in the trigger is a violation of good software engineering principles and will be a maintenance problem.
ALTER TABLE tbl DISABLE TRIGGER trg
http://doc.ddart.net/mssql/sql70/aa-az_5.htm
I don't understand the meaning of your 1st paragraph though
Since you indicate that the trigger contains logic to handle all updates, even manual updates, then that should be where the logic resides. The example you mention, wherein a stored procedure "will take care of this logic" implies duplicate code. Additionally, if you want to be sure that every UPDATE statement has this logic applied regardless of author, then the trigger is the place for it. What happens when someone authors a procedure but forgets to duplicate the logic yet again? What happens when it is time to modify the logic?
Not sure if this is a good idea but it seems to work for me. Transaction should prevent inserts to the table from other processes while trigger is disabled.
IF OBJECT_ID('dbo.TriggerTest') IS NOT NULL
DROP PROCEDURE dbo.TriggerTest
GO
CREATE PROCEDURE [dbo].[TriggerTest]
AS
BEGIN TRANSACTION trnInsertTable1s
;
DISABLE TRIGGER trg_tblTable1_IU ON tblTable1
;
BEGIN -- Procedure Code
PRINT '##trancount'
PRINT ##TRANCOUNT
-- Do Stuff
END -- Procedure Code
;
ENABLE TRIGGER trg_tblTable1_IU ON tblTable1
IF ##ERROR <> 0 ROLLBACK TRANSACTION
ELSE COMMIT TRANSACTION
Do not disable the trigger. You are correct that will disable for any concurrent transactions.
Why do you want to disable the trigger? What does it do? WHy is the trigger casuing a problem? It is usually a bad idea to disable a tigger from a data integrity perspective.
Consider rewriting the trigger to imporve performance if performance is the issue.
I waffled a bit on this one. On the one hand I'm very anti-trigger mostly because it's one more place for me to look for code executing against my table, in addition to the reasons stated in the article linked in the question post.
On the other hand, if you have logic to enforce stable and immutable business rules or cross-table actions (like maintaining a history table) then it would be safer to get this into a trigger so procedure authors and programmers don't need to deal with it - it just works.
So, my recommendation is to put the necessary logic in your trigger rather than in this one proc which, will inevitably grow to several procs with the same exemption.
I just confronted the same problem and came up with the following solution, which works for me.
Create a permanent DB table that contains one record for each trigger that you want to disable (e.g. refTriggerManager); each row contains the trigger name (e.g. strTriggerName = 'myTrigger') and a bit flag (e.g. blnDisabled, default to 0).
At the beginning of the trigger body, look up strTriggerName = 'myTrigger' in refTriggerManager. If blnDisabled = 1, then return without executing the rest of the trigger code, else continue the trigger code to completion.
In the stored proc in which you want to disable the trigger, do the following:
BEGIN TRANSACTION
UPDATE refTriggerManager SET blnDisabled = 1 WHERE strTriggerName = 'myTrigger'
/* UPDATE the table that owns 'myTrigger,' but which you want disabled. Since refTriggerManager.blnDisabled = 1, 'myTrigger' returns without executing its code. */
UPDATE refTriggerManager SET blnDisabled= 0 WHERE triggerName = 'myTrigger'
/* Optional final UPDATE code that fires trigger. Since refTriggerManager.blnDisabled = 0, 'myTrigger' executes in full. */
COMMIT TRANSACTION
All of this takes place within a transaction, so it's isolated from the outside world and won't affect other UPDATEs on the target table.
Does anyone see any problem with this approach?
Bill
I concur with some other answers. Do not disable the trigger.
This is pure opinion, but I avoid triggers like the plague. I have found very few cases where a trigger was used to enforce database rules. There are obvious edge cases in my experience, and I have only my experience on which to make this statement. I have typically seen triggers used to insert some relational data (which should be done from the business logic), for insert data into reporting table ie denormalizing the data (which can be done with a process outside the transaction), or for transforming the data in some way.
There are legitimate uses for triggers, but I think that in everyday business programming they are few and far between. This may not help in your current problem, but you might consider removing the trigger altogether and accomplishing the work the trigger is doing in some other fashion.
you can use 'Exec' function to diable and enable triggers from a stored procedure. Example: EXEC ('ENABLE TRIGGER dbo.TriggerName on dbo.TriggeredTable')

Resources