Trigger calling stored procedure in Oracle - sql

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).

Related

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.

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

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.

What happens when database shut down before triggers executed?

Assume I have table in PostgreSQL as follows:
CREATE TABLE A
(
userid integer,
productid integer,
description citext,
price numeric
)
and some triggers on it:
CREATE TRIGGER afterinsert
AFTER INSERT
ON A
FOR EACH ROW
EXECUTE PROCEDURE DoSomething1();
CREATE TRIGGER beforeinsert
BEFORE INSERT
ON A
FOR EACH ROW
EXECUTE PROCEDURE DoSomething2();
Now, if I do this:
Insert into A values (1,3,'some description',100.5)
What will happen is:
beforeinsert run DoSomething2()
the row is inserted to A
afterinsert run DoSomething1()
My question is what happens if between 2 and 3 the database shuts down?
when it starts again... what will hapen? will it roll back both the inserted row and roll back beforeinsert trigger?
Basically I just don't understand what is considered the Atomic operation in this case. is it the Insert + triggers or just the row?
The triggers are part of the transaction, and it won't commit until they've finished running. If the database shuts down before the transaction commits, it'll be rolled back. The rollback affects all changes that were made in the transaction, including the changes made by the triggers.
If you do a soft shutdown (that is, tell the database to shutdown: pg_ctl -m fast), it will rollback all open transactions. This includes all changes made by any trigger so far.
If you kill the database (like a kill -9), the database has no chance to properly commit or rollback everything. Next time you start the database, it will run a recovery and rollback all the changes to the point of the last successful commit.
All triggers are part of the ongoing transactions, all changes are only committed once all AFTER triggers finish.

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

How does SQL Server treat statements inside stored procedures with respect to transactions?

Say I have a stored procedure consisting of several separate SELECT, INSERT, UPDATE and DELETE statements. There is no explicit BEGIN TRANS / COMMIT TRANS / ROLLBACK TRANS logic.
How will SQL Server handle this stored procedure transaction-wise? Will there be an implicit connection for each statement? Or will there be one transaction for the stored procedure?
Also, how could I have found this out on my own using T-SQL and / or SQL Server Management Studio?
Thanks!
There will only be one connection, it is what is used to run the procedure, no matter how many SQL commands within the stored procedure.
since you have no explicit BEGIN TRANSACTION in the stored procedure, each statement will run on its own with no ability to rollback any changes if there is any error.
However, if you before you call the stored procedure you issue a BEGIN TRANSACTION, then all statements are grouped within a transaction and can either be COMMITted or ROLLBACKed following stored procedure execution.
From within the stored procedure, you can determine if you are running within a transaction by checking the value of the system variable ##TRANCOUNT (Transact-SQL). A zero means there is no transaction, anything else shows how many nested level of transactions you are in. Depending on your sql server version you could use XACT_STATE (Transact-SQL) too.
If you do the following:
BEGIN TRANSACTION
EXEC my_stored_procedure_with_5_statements_inside #Parma1
COMMIT
everything within the procedure is covered by the transaction, all 6 statements (the EXEC is a statement covered by the transaction, 1+5=6). If you do this:
BEGIN TRANSACTION
EXEC my_stored_procedure_with_5_statements_inside #Parma1
EXEC my_stored_procedure_with_5_statements_inside #Parma1
COMMIT
everything within the two procedure calls are covered by the transaction, all 12 statements (the 2 EXECs are both statement covered by the transaction, 1+5+1+5=12).
You can find out on your own by creating a small stored procedure that does something simple, say insert a record into a test table. Then Begin Tran; run sp_test; rollback; Is the new record there? If so, then the SP ignores the outside transaction. If not, then the SP is just another statement executed inside the transaction (which I am pretty sure is the case).
You must understand that a transaction is a state of the session. The session can be in an explicit transaction state because there is at least one BEGIN TRANSACTION that have been executed in the session wherever the command "BEGIN TRANSACTION" has been throwed (before entering in a routine or inside the routine code). Otherwise, the state of the session is in an implicit transaction state. You can have multiple BEGIN TRANSACTION, but only the first one change the behavior of the session... The others only increase the ##TRANCOUNT global sesion variable.
Implicit transaction state means that all SQL orders (DDL, DML and DCL comands) wil have an invisble integrated transaction scope.