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.
Related
I want to remove identity from a column by updating it like this..
sp_configure 'allow update', 1
go
reconfigure with override
go
update sys.syscolumns
set colstat = 0 -- turn off bit 1 which indicates identity column
where id = object_id('tbl1') and name = 'ids'
go
sp_configure 'allow updates', 0
go
reconfigure
go
I am getting this error, tried many times.
Msg 259, Level 16, State 1, Line 15
Ad hoc updates to system catalogs are not allowed.
If you want to get rid of it completely, just rename the table and then dump the data into a new table.
EXEC sp_rename 'OriginalTblName','OLD_OriginalTblName'
CREATE TABLE OriginalTblName (Definition of your Table)
INSERT OriginalTblName
SELECT * FROM OLD_OriginalTblName
You can skip the CREATE TABLE step if you want by just selecting the contents into the new table. You lose the ability to define the fields the way you want with this method.
SELECT * FROM OLD_OriginalTblName
INTO OriginalTblName
If you are just wanting to INSERT new records, you can use IDENTITY INSERT to insert the records you want. Just be careful not to duplicate the values or you will break the table.
SET IDENTITY_INSERT ON OriginalTblName
INSERT OriginalTblName
SELECT someFields
FROM someTbl
SET IDENTITY_INSERT OFF OriginalTblName
IDENTITY INSERT will not work for UPDATE on the IDENTITY field. You will need to capture the data and reinsert the record with one of the methods described above.
I am trying to create an auditing table. I have a table called person.address in the AdventureWorks 2012 database.
I am using a trigger to capture changes to the table, the only problem is I do not know if it is possible to use a trigger to capture a row BEFORE it is edited. I am trying to save resources and overheads so trying to not use a shadow table. I know there is no "Before Insert" trigger. But is there any way to capture the information contained in a row, and when someone does an insert or update, this row can be written to my audit.table before the insert is completed?
Thank you.
Given a simplistic table with two rows:
CREATE TABLE dbo.foo(a INT PRIMARY KEY);
INSERT dbo.foo(a) VALUES(1),(2);
Then an update trigger simply to demonstrate:
CREATE TRIGGER dbo.trfoo ON dbo.foo FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
SELECT a FROM inserted;
SELECT a FROM deleted;
END
GO
The result of an action, such as:
UPDATE dbo.foo SET a += 1;
Results in:
a -- this is the *new* version of these rows
----
3
2
a -- this is the *old* version of these rows
----
2
1
Also, there is an INSTEAD OF INSERT trigger, which allows you to perform actions before the insert (they're not called BEFORE triggers because you still have to perform the insert yourself). More info here.
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.
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
;
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