Where is the flaw in my trigger to override DELETE with a soft-delete? - sql

I have a trigger created with
CREATE TRIGGER [CantDeleteStuff] ON [dbo].[Stuff]
INSTEAD OF DELETE
AS
BEGIN
ROLLBACK
UPDATE [dbo].[Stuff] SET [Deleted]=1 FROM DELETED WHERE [dbo].[Stuff].[Id] = DELETED.[Id]
END
GO
and I think it's intent is clear. But when I try to delete a row I get the error
The transaction ended in the trigger. The batch has been aborted.
How to fix?

Instead of Delete will replace the Delete with your triggering code.
According to Technet, the Rollback in your trigger is the issue. You can read more here.
A trigger operates as if there were an outstanding transaction in effect when the trigger is executed. This is true whether the statement firing the trigger is in an implicit or explicit transaction.
When a statement begins executing in autocommit mode, there is an implied BEGIN TRANSACTION to allow the recovery of all modifications generated by the statement if it encounters an error. This implied transaction has no effect on the other statements in the batch because it is either committed or rolled back when the statement completes. This implied transaction is still in effect, however, when a trigger is called.
When a trigger executes, an implicit transaction is started. If the trigger completes execution and ##TRANCOUNT = 0, error 3609 occurs and the batch is terminated. If a BEGIN TRANSACTION statement is issued in a trigger, it creates a nested transaction. In this situation, when a COMMIT TRANSACTION statement is executed, the statement will apply only to the nested transaction.
When using ROLLBACK TRANSACTION in a trigger, be aware of the following behavior:
All data modifications made to that point in the current transaction are rolled back, including any that were made by the trigger.
The trigger continues executing any remaining statements after the ROLLBACK statement. If any of these statements modify data, the modifications are not rolled back.
A ROLLBACK in a trigger closes and deallocates all cursors that were declared and opened in the batch containing the statement that fired the trigger. This includes cursors declared and opened in stored procedures called by the batch that fired the trigger. Cursors declared in a batch prior to the batch that fired the trigger are only closed. However, STATIC or INSENSITIVE cursors are left open if:
CURSOR_CLOSE_ON_COMMIT is set OFF.
The static cursor is either synchronous or a fully populated asynchronous cursor.
Instead of using ROLLBACK TRANSACTION, the SAVE TRANSACTION statement can be used to execute a partial rollback in a trigger.
https://technet.microsoft.com/en-us/library/ms187844(v=sql.105).aspx
So just remove the Rollback.

Related

Trigger calling stored procedure in Oracle

I have a trigger that calls a stored proc (after insert on a table) to do a number of DML statements. My stored procedure has a commit so my trigger fails. After a bit of researching, I added pragma autonomous_transaction to the trigger and now it doesn't complain. But I'm not sure how this will affect the behaviour of my trigger. Is this the right way to do it or just a "hack" to get it to work?
This is the pseudo code of what I'm trying to do
proc1 (input) - program logic then insert into table X, commit.
proc2 (input) - program logic then insert into table Y, commit.
trigger on table A AFTER INSERT
declare pragma autonomous_transaction
if :new value = 1 then
proc1 (:new value)
else
proc2 (:new value)
end if
Let us consider the program flow:
You start a transaction.
You perform a DML operation on your table.
Your trigger fires and is an autonomous transaction so creates a separate transaction.
Your original transaction continues.
Maybe you perform additional DML actions in that original trasnsaction.
One of those additional DML actions raises an exception.
Your entire transaction (but not the separate autonomous transaction) is rolled back.
However:
You started a separate autonomous transaction.
That calls the stored procedure.
Which does a number of DML statements.
Then the trigger commits them.
You are left in a state where you have two transactions, the original one has been rolled back and the autonomous transaction has been committed.
If the autonomous transaction is used for logging and you want it to always document the actions whether the original transaction failed or was successful then this behaviour is fine.
If the autonomonous transaction is part of the normal business logic then you are potentially left in an inconsistent state where half the transaction was rolled back and half was committed; this is not what you want.
In the latter case, the normal solution would to be to remove the COMMIT from the trigger and make it a non-autonomous transaction and then the DML resulting from the trigger will be part of the original transaction and will be committed (or rolled back) with that transaction.
This is the pseudo code of what I'm trying to do
proc1 (input) - program logic then insert into table X, commit.
proc2 (input) - program logic then insert into table Y, commit.
See: What is the effect of placing the commit after DML in procedure?
In general, you should NOT put a COMMIT in a procedure so your pseudo-code should be:
proc1 (input) - program logic then insert into table X.
proc2 (input) - program logic then insert into table Y.
and your trigger should not be an autonomous transaction.
Then your program flow is:
Insert into table A
Trigger fires
Depending on value, call proc1 or proc2.
Complete the procedure.
Complete the trigger.
Complete the transaction and COMMIT (either from the code that performed the insert or automatically as the transaction closes).

If the table has more than one FOR INSERT trigger and one of them writes to an audit table and the other does a rollback is the audit rolled back

Let's say a table has a validation trigger that enforces some business logic:
TRG_MYTABLE_INSERT_UPDATE_VALIDATION
FOR INSERT, UPDATE on MYTABLE
and an audit trigger that writes all inserts and updates to another table.
TRG_MYTABLE_INSERT_UPDATE_AUDIT
FOR INSERT, UPDATE on MYTABLE
and there's no guarantee that they will be executed in a particular order, will a rollback in the VALIDATION trigger rollback the write to the audit table?
Are all of the triggers enlisted in the same transaction "behind the scenes"?
To answer the question about triggers and transactions: yes triggers are enlisted in the same explicit, or implicit, transaction as the code that executes the statement which makes the trigger fire is enlisted in.
Furthermore, in SQL Server triggers runs be default under XACT_ABORT ON which means that if an error happens in the trigger, the WHOLE transaction is rolled back immediately.
So the answer to your question is that if an error happens in either of the triggers, the whole transaction is rolled back.
You can however do a SET XACT_ABORT OFF in your transaction code, in which case, a rollback would only impact whatever you do in the trigger. That is UNLESS your calling code starts a transaction, and you explicitly do a ROLLBACK in your trigger.
The above is why you should be very careful with using triggers in the first place.

Does autocommit in postgresql mean you cannot have transaction blocks?

if autocommit is on, which is default, postgresql treats each statement as a transaction.
so if i have an explicit transaction block with start transaction and commit, does that mean that block is not executed atomically and cannot be rolled back? (since each statement inside the block would be a transaction in itself due to autocommit).
It's the opposite.
With autocommit turned on, using start transaction is the only way to combine multiple statements into a single transaction.
In fact, Postgres always uses autocommit on the server, unless the client uses start transaction (or begin transaction). When you turn off auto-commit the client will simply send those statements for you automatically.
Quote from the manual
BEGIN initiates a transaction block, that is, all statements after a BEGIN command will be executed in a single transaction until an explicit COMMIT or ROLLBACK is given. By default (without BEGIN), PostgreSQL executes transactions in “autocommit” mode
(Emphasis mine)
BEGIN is the same as START TRANSACTION
Autocommit only affects statements outside of a transaciton. When you do your own start transaction and commit, autocommit has no effect.
When a statement is run outside a transaction, autocommit
ON means that means it is committed when it finishes
OFF means that a new transaction is implicitly started, and an explicit commit is required to commit the statement

When exactly is an AFTER DELETE trigger fired

I hope that you can help me on some SQL theory, as I am not 100% sure how this works.
If I have a trigger and I define it as
AFTER DELETE ON xxxx
I was wondering when exactly this would fire, in terms of transaction management?
So if I delete a record from my table I assume that the trigger will not fire until I type commit and finish the transaction. Is this correct?
If so, then I assume that if the commit on my delete statement works but the trigger fails for some reason then only the trigger would be rolled back, and the original executed delete statement that I performed would still be committed (because I have it defined as AFTER DELETE).
Can somebody please confirm this?
Thanks.
1. You delete a row on TABLE1 no COMMIT;
2. TRIGGER performs an action (This takes place before COMMIT or ROLLBACK for step1, but trigger will not have any commit or rollback in it)
3a. You apply commit - Both step1 and step2 gets completed .
3b. You apply rollback- Both step1 and step2 rolled back.
Either you give 3a or 3b
The purpose of SQL triggers is to ensure referential consistency. But when they would be exectued in a separate transaction commit, there would be the possibility that they leave data in an inconsistent state.
So the delete trigger is executed the moment you do the delete command. When this happens as a transaction and you roll it back, the triggered delete is also rolled back.
An AFTER DELETE trigger is fired after the delete statement is executed, and before the control is returned to the user - i.e., he perceives the delete statement and the code executed after it in a trigger as a single action (assuming the trigger just does DMLs and nothing funky like calling UTL_TCP :-)).
This has nothing to do with transaction management - once the DELETE and the AFTER DELETE trigger execute, you can choose to commit, to rollback, or to continue performing DML statements in the same transaction.

How to Ignoring errors in Trigger and Perform respective operation in MS SQL Server

I have created AFTER INSERT TRIGGER
Now if any case if an error occurs while executing Trigger. It should not effect Insert Operation on Triggered table.
In One word if any ERROR occurs in trigger it should Ignore it.
As I have used
BEGIN TRY
END TRY
BEGIN CATCH
END CATCH
But it give following error message and Rolled back Insert operation on Triggered table
An error was raised during trigger execution. The batch has been
aborted and the user transaction, if any, has been rolled back.
Interesting problem. By default, triggers are designed that if they fail, they rollback the command that fired it. So whenever trigger is executing there is an active transaction, whatever there was an explicit BEGIN TRANSACTION or not on the outside. And also BEGIN/TRY inside trigger will not work. Your best practice would be not to write any code in trigger that could possibly fail - unless it is desired to also fail the firing statement.
In this situation, to suppress this behavior, there are some workarounds.
Option A (the ugly way):
Since transaction is active at the beginning of trigger, you can just COMMIT it and continue with your trigger commands:
CREATE TRIGGER tgTest1 ON Test1 AFTER INSERT
AS
BEGIN
COMMIT;
... do whatever trigger does
END;
Note that if there is an error in trigger code this will still produce the error message, but data in Test1 table are safely inserted.
Option B (also ugly):
You can move your code from trigger to stored procedure. Then call that stored procedure from Wrapper SP that implements BEGIN/TRY and at the end - call Wrapper SP from trigger. This might be a bit tricky to move data from INSERTED table around if needed in the logic (which is in SP now) - probably using some temp tables.
SQLFiddle DEMO
You cannot, and any attempt to solve it is snake oil. No amount of TRY/CATCH or ##ERROR check will work around the fundamental issue.
If you want to use the tightly coupling of a trigger then you must buy into the lower availability induced by the coupling.
If you want to preserve the availability (ie. have the INSERT succeed) then you must give up coupling (remove the trigger). You must do all the processing you were planning to do in the trigger in a separate transaction that starts after your INSERT committed. A SQL Agent job that polls the table for newly inserted rows, an Service Broker launched procedure or even an application layer step are all going to fit the bill.
The accepted answer's option A gave me the following error: "The transaction ended in the trigger. The batch has been aborted.". I circumvented the problem by using the SQL below.
CREATE TRIGGER tgTest1 ON Test1 AFTER INSERT
AS
BEGIN
SET XACT_ABORT OFF
BEGIN TRY
SELECT [Column1] INTO #TableInserted FROM [inserted]
EXECUTE sp_executesql N'INSERT INTO [Table]([Column1]) SELECT [Column1] FROM #TableInserted'
END TRY
BEGIN CATCH
END CATCH
SET XACT_ABORT ON
END