Trigger - is it necessary BEGIN / COMMIT TRAN - sql-server

I want to make INSTEAD OF trigger like this:
CREATE TRIGGER [dbo].[DeleteCompany]
ON [dbo].[Company]
INSTEAD OF DELETE
AS
DECLARE #CompanyID int
SELECT #CompanyID = deleted.CompanyID FROM deleted
BEGIN TRAN
DELETE FROM Project WHERE CompanyID = #CompanyID
DELETE FROM CompanyPerson WHERE CompanyID = #CompanyID
UPDATE PersonCompany SET CompanyID = null WHERE CompanyID = #CompanyID
DELETE [Company]
FROM DELETED D
INNER JOIN [Company] T ON T.CompanyID = D.CompanyID
COMMIT TRAN
So, I can be sure, that these actions is one atomic action. But it make sense or TRIGGER always execute inside transaction?
Also, what happens if company will be deleted inside another TRIGGER like this:
CREATE TRIGGER [dbo].[DeleteSecurityLevel]
ON [dbo].[SecurityLevel]
INSTEAD OF DELETE
AS
DECLARE #SecurityLevelID int
SELECT #SecurityLevelID = deleted.SecurityLevelID FROM deleted
BEGIN TRAN
DELETE FROM Company WHERE SecurityLevelId = #SecurityLevelID
DELETE FROM CompanyRole WHERE SecurityLevelId = #SecurityLevelID
....
DELETE SecurityLevel
FROM DELETED D
INNER JOIN SecurityLevel T ON T.SecurityLevelID = D.SecurityLevelID
COMMIT TRAN
so, trigger DeleteSecurityLevel is deleting Company and call DeleteCompany trigger. It would be in one transaction if each trigger has BEGIN/COMMIT TRAM ? if each trigger does not have it?
PS. I can't set "CASCADE DELETE" because DB has some relationships like it:
so, try to set CASCADE DELETE will throw error like it:
Introducing FOREIGN KEY constraint 'FK_Persons_Areas' on table
'Persons' may cause cycles or multiple cascade paths. Specify ON
DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints. Could not create constraint or index. See previous
errors.

All DML statements are executed within a transaction. The DML within the trigger will use the transaction context of the statement that fired the trigger so all modifications, inside the trigger and out, are a single atomic operation.

You don't need explicit transactions inside the trigger, they share the same transaction workspace and the batch inside the trigger and the operation invoking it either commit together or rollback together

A trigger always executes in the context of a transaction - every DML statement operates within a transaction. This is normally hidden from view by the fact that Implicit Transactions are set to commit automatically in SQL Server.
If you issue a rollback from within a trigger, this will (as always with rollback) rollback all transactions, whether nested or not.
In general, you wouldn't commit within a trigger, unless (as in your commented out example) you're opening a nested transaction explicitly.
If there are other aspects to your question, I'm unable to work out what they are from your posted example. Although I'm always a fan of people posting actual SQL when asking SQL questions, sometimes a little commentary, or a bullet-point list of actual questions can help.

Related

"Instead of delete trigger" triggers two times

I am using MS SQL Server 2016 where I have implemented a instead of delete trigger. It looks like this:
ALTER TRIGGER MyTrigger ON MyTable INSTEAD OF DELETE AS
BEGIN
IF --some condition
BEGIN
RAISERROR ('Error msg', 16, 1)
ROLLBACK TRAN
RETURN
END
DELETE MyTable FROM MyTable JOIN deleted ON MyTable.id = deleted.id
END
If I execute a DELETE statement on the table 'MyTable' and the condition in the if is not fulfilled the DELETE statement is executed after the if-block. This is absolutely correct. But in the console of SSMS it is written twice that the DELETE statement was executed. So the following is written in the console:
(1 rows affected)
(1 rows affected)
I do not understand why. Why does SSMS indicate twice that a row is affected? I use SSMS version 15.0.18338.0.
This is because there were 2 sets of data effect, the set outside the TRIGGER, and then again inside it, because the initial dataset doesn't perform the DML operation itself. If you don't want to see the latter count, turn NOCOUNT to ON. This, of course, means that if fewer rows are effected in your TRIGGER, you won't know about it in the output from SSMS (but it's just informational anyway).
It is also heavily advised that you don't use ROLLBACK inside a TRIGGER, handle transactions outside the TRIGGER, not inside. RAISERROR isn't recommend either and you should be using THROW for new development work (that's been recommended since 2012!). This results in a TRIGGER like below:
CREATE OR ALTER TRIGGER MyTrigger ON dbo.MyTable INSTEAD OF DELETE AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM deleted WHERE SomeVal = 'Nonsense')
THROW 95302, N'Error Msg', 16; --Use an error number appropriate for you
ELSE
DELETE MT FROM dbo.MyTable MT JOIN deleted ON MT.id = deleted.id;
END;
GO

SQL Server Trigger DELETE only a few records [duplicate]

I want to create a trigger to check what is being deleted against business rules and then cancel the deletion if needed. Any ideas?
The solution used the Instead of Delete trigger. The Rollback tran stopped the delete. I was afraid that I would have a cascade issue when I did the delete but that didn't seem to happen. Maybe a trigger cannot trigger itself.
Use an INSTEAD OF DELETE (see MSDN) trigger and decide within the trigger what you really want to do.
The solution used the Instead of Delete trigger. The Rollback tran stopped the delete. I was afraid that I would have a cascade issue when I did the delete but that did'nt seem to happen. Maybe a trigger cannot trigger itself. Anyhow, thanks all for your help.
ALTER TRIGGER [dbo].[tr_ValidateDeleteForAssignedCalls]
on [dbo].[CAL]
INSTEAD OF DELETE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #RecType VARCHAR(1)
DECLARE #UserID VARCHAR(8)
DECLARE #CreateBy VARCHAR(8)
DECLARE #RecID VARCHAR(20)
SELECT #RecType =(SELECT RecType FROM DELETED)
SELECT #UserID =(SELECT UserID FROM DELETED)
SELECT #CreateBy =(SELECT CreateBy FROM DELETED)
SELECT #RecID =(SELECT RecID FROM DELETED)
-- Check to see if the type is a Call and the item was created by a different user
IF #RECTYPE = 'C' and not (#USERID=#CREATEBY)
BEGIN
RAISERROR ('Cannot delete call.', 16, 1)
ROLLBACK TRAN
RETURN
END
-- Go ahead and do the update or some other business rules here
ELSE
Delete from CAL where RecID = #RecID
END
The trigger can roll back the current transaction, which will have the effect of cancelling the deletion. As the poster above also states, you can also use an instead of trigger.
According to MSDN documentation about INSTEAD OF DELETE triggers:
The deleted table sent to a DELETE
trigger contains an image of the rows
as they existed before the DELETE
statement was issued.
If I understand it correctly the DELETE is actually being executed. What am I missing?
Anyway, I don't understand why do you want to delete the records and if the business rules are not passed then undelete those records. I would have swear it should be easier to test if you pass the business rules before deleting the records.
And I would have said use a transaction, I haven't heard before about INSTEAD OF triggers.

is this correct this intead of delete trigger? and other questions about tirggers

I need to to delete the children of a table when I delete the parent. The common Order/details example.
I use this instead trigger:
CREATE TRIGGER trg_OderDelete
ON Oders
INSTEAD OF DELETE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
Delete from Details where IDOrder IN(select deleted.IDOrder from deleted)
Delete from Orders where IDOrder IN(select deleted.IDOrder from deleted)
END
GO
First, I try with only the fist delete statement, but only delete de details, not the orders, so I added the second delete to delete the order too.
My doubt is if this is correct or not. I mean that if the trigger is executed when I delete an order, why have I to added the delete statement in the trigger to delete the order that execute the trigger?
I also would like to know this:
1.- is a transaction? I mean that if I delete the details and for some reason the order can't be deleted, finally the details are not deleted?
2.- This transaction avoid to added new details when I delete the ordener? Imagine that I want to delete an order, in the trigger is executed the first delete, the details, but before is executed the second delete, the order, other user try to added a new detail. This detail is added or not because the order is blocked in a transaction?
Thanks.
why have I to added the delete statement in the trigger to delete the order that execute the trigger?
Because it's an INSTEAD OF trigger - you've told SQL Server that you'll take responsibility for performing the delete.
is a transaction? I mean that if I delete the details and for some reason the order can't be deleted, finally the details are not deleted?
The outer DELETE that caused this trigger to fire will either already be in a transaction or will have caused one to start. If an error occurs that causes your trigger to abort between the two deletes, and the caller has a proper strategy for dealing with errors (e.g. XACT_ABORT is ON; or they're using TRY/CATCH or checking ##ERROR and calling ROLLBACK) then your first delete will be rolled back when the transaction rolls back.
If, OTOH, they're ignoring errors and committing transactions anyway, then you could end up with the Details rows still having been deleted.
This transaction avoid to added new details when I delete the ordener? Imagine that I want to delete an order, in the trigger is executed the first delete, the details, but before is executed the second delete, the order, other user try to added a new detail. This detail is added or not because the order is blocked in a transaction?
The DELETE against Details will have taken an Exclusive lock on that table. Nobody is going to be able to execute an INSERT/UPDATE/DELETE against that table until the transaction commits.

Does a rollback inside a INSERT AFTER or UPDATE AFTER trigger rollback the entire transaction

Does a rollback inside a INSERT AFTER or an UPDATE AFTER trigger rollback the entire transaction or just the current row that is the reason for trigger, and is it same with Commit ?
I tried to check it through my current projects code which uses MSTDC for transactions, and it appears as if though the complete transaction is aborted.
If a Rollback in the trigger does rollback the entire transaction, is there a workaround for to restrict it just the current rows.
I found a link for sybase on this, but nothing on sql server
Yes it will rollback the entire transaction.
It's all in the docs (see Remarks). Note the comment I've emphasised - that's pretty important I would say!!
If a ROLLBACK TRANSACTION is issued in a trigger:
All data modifications made to that point in the current transaction
are rolled back, including any 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. No nested triggers are fired by the
execution of these remaining statements.
The statements in the batch after the statement that fired the trigger
are not executed.
As you've already been let to know, the ROLLBACK command can't possibly be modified/tuned so that it only roll back the statements issued by the trigger.
If you do need a way to "rollback" actions performed by the trigger only, you could,
as a workaround, consider modifying your trigger in such a way that before performing the actions, the trigger makes sure those actions do not produce exceptional situations that would cause the entire transaction to rollback.
For instance, if your trigger inserts rows, add a check to make sure the new rows do not violate e.g. unique constraints (or foreign key constraints), something like this:
IF NOT EXISTS (
SELECT *
FROM TableA
WHERE … /* a condition to test if a row or rows you are about
to insert aren't going to violate any constraint */
)
BEGIN
INSERT INTO TableA …
END;
Or, if your trigger deletes rows, check if it doesn't attempt to delete rows referenced by other tables (in which case you typically need to know beforehand which tables might reference the rows):
IF NOT EXISTS (
SELECT * FROM TableB WHERE …
)
AND NOT EXISTS (
SELECT * FROM TableC WHERE …
)
AND …
BEGIN
DELETE FROM TableA WHERE …
END
Similarly, you'd need to make checks for update statements, if any.
Any rollback command will roll back everything till the ##trancount is 0 unless you specify some savepoints and it doesnt matter where you put rollback tran command.
The best way is to look into the code once again and confirm business requirement and see why do you need a rollback in trigger?

SQL Server: pause a trigger

I am working with SQL Server 2005 and I have trigger on a table that will copy an deletions into another table. I cannot remove this trigger completely. My problem is that we have now developed an archiving strategy for this table. I need a way of "pausing" a trigger when the stored proc that does the archiving runs.
A little more detail would be useful on how the procedure is accessing the data, but assuming you are just getting the data, then deleting it from the table and wish to disable the trigger for this process, you can do the following
DISABLE TRIGGER trg ON tbl;
then
ENABLE TRIGGER trg ON tbl;
for the duration of the procedure.
This only works for SQL 2005+
An alternative method is to use Context_Info to disable it for a single session, while allowing other sessions to continue to fire the trigger.
Context_Info is a variable which belongs to the session. Its value can be changed using SET Context_Info.
The trigger will mostly look like this:
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)
Before issuing the INSERT statement, the context info is set to a value. In the trigger, we are first checking if the value of context info is the same as the value declared. If yes, the trigger will simply return without executing its code, otherwise the trigger will fire.
source: http://www.mssqltips.com/tip.asp?tip=1591
if DISABLE TRIGGER/ENABLE TRIGGER is not an option for some reason, you can create a table with a single row which will serve as a flag for the trigger.

Resources