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.
Hi I have a database where we often need to delete records, however as this can sometimes be mistakenly done I was wondering is there a way to save a sort of history of SQL commands done, and the output to screen log of them? Thanks
Try this:
USE YourDatabase;
GO
CREATE TABLE ddl_log (PostTime datetime, DB_User nvarchar(100), Event nvarchar(100), TSQL nvarchar(2000));
GO
CREATE TRIGGER log
ON DATABASE
FOR DDL_DATABASE_LEVEL_EVENTS
AS
DECLARE #data XML
SET #data = EVENTDATA()
INSERT ddl_log
(PostTime, DB_User, Event, TSQL)
VALUES
(GETDATE(),
CONVERT(nvarchar(100), CURRENT_USER),
#data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)'),
#data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(2000)') ) ;
GO
--Test the trigger
CREATE TABLE TestTable (a int)
DROP TABLE TestTable ;
GO
SELECT * FROM ddl_log ;
GO
Reference
I see that you are using Sql Server.
You can write a trigger as someone suggest or you can configure Change Data Capture. CDC will have better performance.
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.
We began with SQL Server 2005 database and tables. [UPDATE, INSERT and DELETE] in this case we were using the UPDATE trigger(s) to insert rows into audit tbl(s) when application (VB6) data table is modified. We moved the audit tables to SQL Server 2008. The only change in the trigger statement(s) (on the SQL Server 2005) we modified the original ([FHA-4]) to the new (SQL Server 2008 [FHA-DMZ-CL1SQL]) server name.
When the trigger is activated the hour glass stays on until a sql timeout message appears and the application aborts. When checking the audit tables nothing new is added so the insert did not work.
Here is the actual trigger statement for the table:
USE [BCC_DHMH]
GO
/****** Object: Trigger [dbo].[TriggerAddressUpdate] Script Date: 04/07/2010 09:47:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
--Logic to save to the table that supports Tripwire
ALTER TRIGGER [dbo].[TriggerAddressUpdate]
ON [dbo].[tblAddress]
AFTER UPDATE
AS
SET XACT_ABORT ON
BEGIN DISTRIBUTED TRANSACTION
SET NOCOUNT ON;
--IF (SYSTEM_USER <> 'FHA\kYarberough' AND SYSTEM_USER <> 'FHA\ljlee' AND SYSTEM_USER <> 'FHA\PHarvey' AND SYSTEM_USER <> 'FHA\BShenosky' AND SYSTEM_USER <> 'FHA\BBrodie' AND SYSTEM_USER <> 'FHA\DRandolph')
Declare #UpdateID as varchar(50)
Set #UpdateID = newid()
BEGIN
INSERT [FHA-4].[ECMS_Audit].[dbo].[tblAddress_Audit]
([fldAddressOwnerID], [fldUpdateID], [fldAddressTypeCode], [fldAddressMailcode], [fldAddressSequence],
[fldAddressID], [fldName], [fldLine1], [fldLine2], [fldCity], [fldState], [fldCounty],
[fldZipcode], [fldWorkFax], [fldWorkPhone], [fldWorkExtension], [fldWorkEMail], [fldHomePhone],
[fldHomeEMail], [fldContactName], [fldContactPhone], [fldContactFax], [fldContactExtension], [fldEffectiveDate],
[fldExpirationDate], [fldUpdateTimestamp], [fldUpdateUserID], [fldRelationship], [fldNotes], [fldNCPDPNum],
[fldMedicaidNum], [fldStoreNum],
[ModifiedBySqlUser], [ModifiedByNTUser], [ModifiedDate], [Action] )
SELECT [fldAddressOwnerID], #UpdateID, [fldAddressTypeCode], [fldAddressMailcode], [fldAddressSequence],
[fldAddressID], [fldName], [fldLine1], [fldLine2], [fldCity], [fldState], [fldCounty],
[fldZipcode], [fldWorkFax], [fldWorkPhone], [fldWorkExtension], [fldWorkEMail], [fldHomePhone],
[fldHomeEMail], [fldContactName], [fldContactPhone], [fldContactFax], [fldContactExtension], [fldEffectiveDate],
[fldExpirationDate], [fldUpdateTimestamp], [fldUpdateUserID], [fldRelationship], [fldNotes], [fldNCPDPNum],
[fldMedicaidNum], [fldStoreNum],
CURRENT_USER, SYSTEM_USER, GETDATE(), 'InitialValues' FROM deleted
INSERT [FHA-4].[ECMS_Audit].[dbo].[tblAddress_Audit]
([fldAddressOwnerID], [fldUpdateID], [fldAddressTypeCode], [fldAddressMailcode], [fldAddressSequence],
[fldAddressID], [fldName], [fldLine1], [fldLine2], [fldCity], [fldState], [fldCounty],
[fldZipcode], [fldWorkFax], [fldWorkPhone], [fldWorkExtension], [fldWorkEMail], [fldHomePhone],
[fldHomeEMail], [fldContactName], [fldContactPhone], [fldContactFax], [fldContactExtension], [fldEffectiveDate],
[fldExpirationDate], [fldUpdateTimestamp], [fldUpdateUserID], [fldRelationship], [fldNotes], [fldNCPDPNum],
[fldMedicaidNum], [fldStoreNum],
[ModifiedBySqlUser], [ModifiedByNTUser], [ModifiedDate], [Action] )
SELECT [fldAddressOwnerID], #UpdateID, [fldAddressTypeCode], [fldAddressMailcode], [fldAddressSequence],
[fldAddressID], [fldName], [fldLine1], [fldLine2], [fldCity], [fldState], [fldCounty],
[fldZipcode], [fldWorkFax], [fldWorkPhone], [fldWorkExtension], [fldWorkEMail], [fldHomePhone],
[fldHomeEMail], [fldContactName], [fldContactPhone], [fldContactFax], [fldContactExtension], [fldEffectiveDate],
[fldExpirationDate], [fldUpdateTimestamp], [fldUpdateUserID], [fldRelationship], [fldNotes], [fldNCPDPNum],
[fldMedicaidNum], [fldStoreNum],
CURRENT_USER, SYSTEM_USER, GETDATE(), 'NewValues' FROM inserted
END
COMMIT TRANSACTION
SET XACT_ABORT OFF
well that trigger appears to have the old name to me. But if it really does have the new name...hmmm...
Since it is a distributed transaction are you sure you have the linked server set up correctly?
Also I'd prefer not to use a distributed transaction in a trigger, it could affect users being able to change records if the other server is down. MIght be better to send the records to an audit table on the same server or to a staging table that runs a job to move the records to the other server.
How can i know which sql statement fired through trigger for select, insert, update and delete on table?
As Jonas says, Profiler is your best option (and only option for SELECT queries). For INSERT, UPDATE, DELETEs, the closest you can get without Profiler may be to look at the input buffer via DBCC INPUTBUFFER(##SPID). This will only work for ad-hoc language events, not RPC calls, and will only show you the first 256 characters of the SQL statement (depending on version, I believe). Some example code, (run as dbo):
CREATE TABLE TBL (a int, b varchar(50))
go
INSERT INTO TBL SELECT 1,'hello'
INSERT INTO TBL SELECT 2,'goodbye'
go
GRANT SELECT, UPDATE ON TBL TO guest
go
CREATE TABLE AUDIT ( audittime datetime default(getdate())
, targettable sysname
, loginname sysname
, spid int
, sqltext nvarchar(max))
go
CREATE TRIGGER TR_TBL ON TBL FOR INSERT, UPDATE, DELETE
AS BEGIN
CREATE TABLE #DBCC (EventType varchar(50), Parameters varchar(50), EventInfo nvarchar(max))
INSERT INTO #DBCC
EXEC ('DBCC INPUTBUFFER(##SPID)')
INSERT INTO AUDIT (targettable, loginname, spid, sqltext)
SELECT targettable = 'TBL'
, suser = suser_name()
, spid = ##SPID
, sqltext = EventInfo
FROM #DBCC
END
GO
/* Test the Audit Trigger (can be run as guest) */
UPDATE TBL SET a = 3 WHERE a = 2
First, there are no select dml triggers, only triggers that work on INSERT, UPDATE or DELETE
Secondly, you can't know which sql statement triggered the trigger (at least not in the trigger). However, you can use profiler to debug what's happening in the database. There's a decent explanation of this here.