Set trigger order usage - sql

we can set order option of trigger to first, last or none by use sp_settriggerorder command. I want to know what's significance of set trigger order. and when necessity using this option?
I use SQL Server 2008 R2.

One purpose would be if you want one trigger to perform some form of sanity checks before other triggers (which may have expensive actions) fire. If the first trigger causes a ROLLBACK to occur, the other triggers aren't fired:
create table T (
ID int not null
)
go
create trigger T1
on T
after insert
as
RAISERROR('T1',10,1) WITH NOWAIT
go
create trigger T2
on T
after insert
as
RAISERROR('T2',10,1) WITH NOWAIT
go
create trigger T3
on T
after insert
as
RAISERROR('T3',10,1) WITH NOWAIT
go
sp_settriggerorder 'T1','First','Insert'
go
sp_settriggerorder 'T3','Last','Insert'
go
insert into T(ID) values (1)
--T1
--T2
--T3
--(1 row(s) affected)
go
alter trigger T1
on T
after insert
as
RAISERROR('T1',10,1) WITH NOWAIT
ROLLBACK TRANSACTION
go
insert into T(ID) values (2)
--T1
--Msg 3609, Level 16, State 1, Line 1
--The transaction ended in the trigger. The batch has been aborted.

It specifies in what order AFTER triggers fire when multiple triggers apply to a SQL statement. You can specify only one first or last trigger for a specific table, database or server.

Related

Alternative to enable and disable trigger

What is the alternative to enabling and disabling trigger for this example?
This is my trigger and I have a scenario where it should not be allowed to do update directly column Column33 in Table33 (by executing query UPDATE Table33 SET Column33='123'), but it should be allowed to do update of that column from other triggers that are doing some calculations. It works when I disable trigger Triger33 in other triggers, do the update inside triggers and then enable this trigger again. This solution is not good. Is there a way that I can put some kind of a condition to this trigger so that it allows updates that are not direct so that I can avoid disabling and enabling in other triggers?
create trigger [dbo].[Trigger33]
on [dbo].[Table33]
AFTER UPDATE
as
if UPDATE(Column33)
begin
raiserror('It is not allowed to update Column33 in Table33', 16,1)
rollback
end
You can allow trigger code to globally bypass other triggers with the nested triggers server configuration option, or the NESTED_TRIGGERS database configuration option (for partially contained databases).
eg
EXEC sp_configure 'show advanced options', 1;
GO
RECONFIGURE ;
GO
EXEC sp_configure 'nested triggers', 0 ;
GO
RECONFIGURE;
GO
create table t(id int primary key)
create table t2(id int primary key)
go
create trigger te_nope on t2 after insert
as
begin
throw 50001, 'nope', 1;
end
go
create trigger t_ins on t after insert
as
begin
insert into t2(id) select id from inserted
end
go
insert into t2(id) values (1) --fails
--Msg 50001, Level 16, State 1, Procedure te_nope, Line 4 [Batch Start Line 32]
--nope
insert into t(id) values (1) --suceeds
--(1 row affected)
--
--(1 row affected)
Other options include shoving a secret into CONTEXT_INFO that's checked in the inner trigger, or performing an impersonation that is checked to modify the behavior of nested triggers.

Subqueries are not allowed in this context

I want to prevent further duplicates from being added to my table while allowing existing duplicates to remain. I thought I could accomplish this using a filtered index as follows.
But when I execute the following query:
CREATE UNIQUE INDEX IX_Account
ON Holdings(Account)
WHERE Account NOT IN (select Account from Holdings)
I get the following error:
Msg 1046, Level 15, State 1, Line 57
Subqueries are not allowed in this context. Only scalar expressions are allowed.
How can I prevent further duplicates from being added?
You can't have your cake and eat it.
Either
decide that your data should have integrity and purge the duplicated before adding the unique index (filtering it for the reason you mention does not make sense)
or
enforce your logic with an insert trigger:
create trigger no_more_duplicates on Holdings
after insert as
if exists
(
select 1
from inserted
where inserted.Account IN (select Account from Holdings)
)
raiserror('Cannot add duplicates',16,0)
end -- trigger
This trigger's a bit dumb, it will not prevent duplicates on a multiple-row insert, nor will it let the nonduplicate ones be saved. Yet, it's enough that you get the picture.
raiserror in a trigger will not automatically rollback the transaction, but throw will. Alternatively you can raiserror and rollback.
Also with an AFTER trigger the data in the INSERTED virtual table is already present in the table. So a trigger would need to look like:
use tempdb
drop table if exists Holdings
create table Holdings(id int primary key, Account int)
go
create or alter trigger no_more_duplicates on Holdings
after insert as
begin
if exists
(
select 1
from inserted
where inserted.Account IN (select Account from Holdings where id <> inserted.id)
)
begin
throw 60000, 'Cannot add duplicates', 1 ;
--raiserror('Cannot add duplicates',16,1)
end;
end -- trigger
go
insert into Holdings(id,Account) values (1,1)
go
insert into Holdings(id,Account) values (2,1)
go
select * from holdings

Deleted Virtual table in SQL Server is empty ON Delete trigger

I am trying to invoke an ON Delete trigger event, but when I try to select row from the Deleted table, it is empty.
Is there any other way to get Id of row on which delete event has fired?
Code:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER TRIGGER [audit]
ON [dbo].[S_PARTY]
FOR DELETE
AS
-- Insert statements for trigger here
DECLARE #id nvarchar(50)
SET #id = (Select ROW_ID from deleted )
BEGIN
-- Insert statements for trigger here
INSERT INTO [dbo].[S_AMF_AUDIT_ITEM] (ROW_ID)
VALUES (#id);
END
When trigger fires on this, I am getting error as S_AMF_AUDIT_ITEM doesn't allow null values. So can you please help me this to get Id of table on which delete command executes?
Your trigger is broken.
It doesn't take into account that the DELETE statement might affect zero or more than one rows.
The issue with NULL could occur for the statement DELETE FROM [dbo].[S_PARTY] WHERE 1 = 0.
A fixed version would be
ALTER TRIGGER [audit]
ON [dbo].[S_PARTY]
FOR DELETE
AS
BEGIN
INSERT INTO [dbo].[S_AMF_AUDIT_ITEM]
(ROW_ID)
SELECT ROW_ID
FROM deleted;
END
A trigger can fire for 0..N deleted rows. For example:
delete YourTable
where 1=2
Would run a for delete trigger on YourTable. So a trigger has to be able to deal with zero or multiple rows. Consider rewriting your trigger like:
ALTER TRIGGER [audit] ON [dbo].[S_PARTY] FOR DELETE
AS
INSERT dbo.S_AMF_AUDIT_ITEM
(ROW_ID)
SELECT ROW_ID
FROM deleted
That will work for any number of inserted rows.

SQL Server 2008 MERGE statement - how to disable INSTEAD OF INSERT trigger to allow the MERGE

I am attempting to use the SQL SERVER 2008 MERGE statement in a stored procedure for updating/inserting a table.
I have an INSTEAD OF INSERT trigger on the table, and I get the following error message when attempting to CREATE the procedure
The target 'Phone' of the MERGE statement has an INSTEAD OF trigger on some,
but not all, of the actions specified in the MERGE statement. In a MERGE statement, if any action has an enabled INSTEAD OF trigger on the target, then all actions must have enabled INSTEAD OF triggers.
I definitely do not need an INSTEAD OF UPDATE trigger, (and can't create one because of CASCADE DELETES being enabled on the table).
So in the stored procedure I first issue a DISABLE TRIGGER command before the MERGE. But when I run the stored proc, I get the same error, as if the DISABLE TRIGGER command never gets run.
The Query Optimizer does a static parse of your T-SQL batch, and as soon as it sees the MERGE statement, it will validate the requirements. It will NOT factor in any DDL statements that affect the triggers before the MERGE statement.
You can work around this using GO to break the statements into separate batches, but if it is in a single SP (no GO statements), you have two choices
put the MERGE into a support SP that the main one calls; or
use dynamic SQL
Dynamic SQL
Let's create a table with a trigger
create table tg1(i int)
;
create trigger tg1_tg on tg1 instead of insert as
select 1
GO
Then attempt to MERGE on the table
alter table tg1 disable trigger tg1_tg
;
merge tg1 as target
using (select 1 union all select 3) as source (X) on target.i = source.x
when matched then
delete
when not matched by target then
insert (i) values (x)
output $action, inserted.*, deleted.*
;
alter table tg1 enable trigger tg1_tg
;
Not good..
Msg 5316, Level 16, State 1, Line 1
The target 'tg1' of the MERGE statement has an INSTEAD OF trigger on some, but not all, of the actions specified in the MERGE statement. In a MERGE statement, if any action has an enabled INSTEAD OF trigger on the target, then all actions must have enabled INSTEAD OF triggers.
So we use dynamic SQL
alter table tg1 disable trigger tg1_tg
;
exec ('
merge tg1 as target
using (select 1 union all select 3) as source (X) on target.i = source.x
when matched then
delete
when not matched by target then
insert (i) values (x)
output $action, inserted.*, deleted.*
;')
alter table tg1 enable trigger tg1_tg
;
Support procedure
Let's create a procedure that will perform the MERGE (a production proc probably would have a table variable, use a #temp table or take in some parameters)
create proc tg1_MERGE as
merge tg1 as target
using (select 1 union all select 3) as source (X) on target.i = source.x
when matched then
delete
when not matched by target then
insert (i) values (x)
output $action, inserted.*, deleted.*
;
GO
No go...
Msg 5316, Level 16, State 1, Line 1
The target 'tg1' of the MERGE statement has an INSTEAD OF trigger on some, but not all, of the actions specified in the MERGE statement. In a MERGE statement, if any action has an enabled INSTEAD OF trigger on the target, then all actions must have enabled INSTEAD OF triggers.
Even to create it, you need to disable the triggers - so disable the trigger and create the proc again - it will work this time around.
Finally, you can run this batch which works
alter table tg1 disable trigger tg1_tg
;
exec tg1_MERGE
;
alter table tg1 enable trigger tg1_tg
;

how to create before update trigger in sql server 2005

Is there anyway where I can create a trigger which will execute before the update/delete takes place( and then the actual update/delete takes place)? and how can I drop a trigger from a table?
to drop a trigger use:
--SQL Server 2005+, drop the trigger, no error message if it does not exist yet
BEGIN TRY DROP TRIGGER dbo.TrigerYourTable END TRY BEGIN CATCH END CATCH
GO
--drop trigger pre-SQl Server 2005, no error message if it does not exist yet
if exists (select * from sysobjects where id = object_id(N'[dbo].[TrigerYourTable ]') and OBJECTPROPERTY(id, N'IsTrigger') = 1)
drop trigger [dbo].[TrigerYourTable ]
GO
OP said in a comment:
...suppose I have to check childcount of
a perticular user if that is more than
5 do not update the user.how can I do
that using instead of trigger?
You don't really need to prevent the original update, you can let it happen, and then in the trigger check for the problem and rollback if necessary. This is how to enforce the logic for one or many affected rows, when you need to JOIN to determine the childcount of the affected rows:
--create the trigger
CREATE TRIGGER dbo.TrigerYourTable ON dbo.YourTable
AFTER UPDATE
AS
SET NOCOUNT ON
IF EXISTS (SELECT
1
FROM INSERTED i
INNER JOIN YourChildrenTable c ON i.ParentID=c.ParentID
GROUP BY i.ParentID
HAVING COUNT(i.ParentID)>5
)
BEGIN
RAISERROR('Count of children can not exceed 5',16,1)
ROLLBACK
RETURN
END
GO
It will throw the error if there is a violation of the logic, and the original command will be subject to a rollback.
If childcount is a column within the affected table, then use a trigger like this to enforce the logic:
--create the trigger
CREATE TRIGGER dbo.TrigerYourTable ON dbo.YourTable
AFTER UPDATE
AS
SET NOCOUNT ON
IF EXISTS (SELECT 1 FROM INSERTED WHERE childcount>5)
BEGIN
RAISERROR('Count of children can not exceed 5',16,1)
ROLLBACK
RETURN
END
GO
If you just want to ignore the update for any rows that violate the rule try this:
--create the trigger
CREATE TRIGGER dbo.TrigerYourTable ON dbo.YourTable
INSTEAD OF UPDATE
AS
SET NOCOUNT ON
UPDATE y
SET col1=i.col1
,col2=i.col2
,col3=i.col3
,.... --list all columns except the PK column!
FROM dbo.YourTable y
INNER JOIN INSERTED i on y.PK=i.PK
WHERE i.childcount<=5
GO
It will only update rows that have a child count less than 5, ignoring all affected rows that fail the requirement (no error message).
This article from microsoft explains the syntax of creating triggers.
http://msdn.microsoft.com/en-us/library/ms189799.aspx
There isn't really a 'before' trigger, but you can use an INSTEAD OF trigger that allows you to jump in place of whatever action is attempted, then define your own action.
I've used that technique for versioning data.
CREATE TRIGGER [dbo].[Documents_CreateVersion]
ON [dbo].[Documents]
INSTEAD OF UPDATE
AS
BEGIN
DECLARE #DocumentID int
SELECT DocumentID = DocumentID FROM INSERTED
-- do something
END
INSERTED is a bit of a misnomer here, but it contains the details of the action before it occurs you can then define your own action with that data.
Edit:
As per comments below my response, my example can be dangerous if multiple rows are updated at once. My application doesn't allow for this so it's fine in this case. I would agree that the above is a bad practice regardless.
to drop trigger- use database_name
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'tgr_name' AND type = 'TR')
DROP TRIGGER tgr_name
GO
Here's a simple trigger that checks columns values, and fires before updating or inserting, and raises an error.
IF OBJECT_ID ('dbo.MyTableTrigger', 'TR') IS NOT NULL
DROP TRIGGER dbo.MyTableTrigger;
GO
CREATE TRIGGER MyTableTrigger
ON dbo.MyTable
FOR INSERT, UPDATE
AS
DECLARE #Col1ID INT
DECLARE #Col2ID INT
SELECT #Col1ID = Col1ID, #Col2ID = Col2ID FROM inserted
IF ((#Col1ID IS NOT NULL) AND (#Col2ID IS NOT NULL))
BEGIN
RAISERROR ('Col1ID and Col2ID cannot both be in MyTable at the same time.', 16, 10);
END