Triggers Part 2: Facts and Frequently-Occuring-Problems (FOP)
This is part of a 3 part epic mini-series on triggers, view Part 1 and Part 3.
When are triggers fired, and how many times?
For your standard missionary position trigger, they are fired once per batch. So if you have an update statement that affects 57 rows, then the DML AFTER trigger fires *once* but the deleted and inserted magic tables have 57 rows in them. One common anti-pattern is not handling multiple values in the inserted/deleted tables. Code to prove the firing behavior of triggers:
-- Test trigger firing cardinality
IF OBJECT_ID('TestTriggerFiring', N'U') > 0
DROP TABLE TestTriggerFiring
IF OBJECT_ID('TestTriggerAudit', N'U') > 0
DROP TABLE TestTriggerAudit
IF OBJECT_ID('tr_TestTriggerFiring', N'TR') > 0
DROP TRIGGER dbo.tr_TestTriggerFiring
CREATE TABLE TestTriggerFiring
(
KeyID INT PRIMARY KEY CLUSTERED,
Payload VARCHAR(MAX) NOT NULL,
LastUpdatedDate DATETIME NULL,
LastUpdatedBy sysname NULL
)
CREATE TABLE TestTriggerAudit
(
TriggerName SYSNAME,
DateFired DATETIME NOT NULL,
ActionType VARCHAR(10) NOT NULL,
KeyID INT NOT NULL,
Stamp UNIQUEIDENTIFIER
)
GO
CREATE TRIGGER dbo.tr_TestTriggerFiring on dbo.TestTriggerFiring
FOR INSERT, UPDATE
AS
DECLARE @MyGuid uniqueidentifierset @MyGuid = NEWID()
INSERT INTO TestTriggerAudit (TriggerName, DateFired, ActionType, KeyID, Stamp)
SELECT 'tr_TestTriggerFiring', GETDATE(), 'inserted', i.KeyID, @MyGuid
FROM INSERTED i
GO
/*
DELETE FROM TestTriggerFiring
DELETE FROM TestTriggerAudit
*/
INSERT INTO TestTriggerFiring
SELECT TOP 3 object_id, name, null, null
FROM sys.objects
GO
INSERT INTO TestTriggerFiring values (-1, 'test4', null, null)
WAITFOR DELAY '00:00:001'
GO
INSERT INTO TestTriggerFiring values (-2, 'test5', null, null)
WAITFOR DELAY '00:00:001'
GO
INSERT INTO TestTriggerFiring values (-3, 'test6', null, null)
WAITFOR DELAY '00:00:001'
GO
SELECT * FROM TestTriggerAudit
What are the inserted/deleted magic tables? How do they work?
The inserted and deleted tables hold the date being changed. Depending on what you are doing they hold different data:
Insert
Inserted – holds the new data
Deleted – is empty
Update
Inserted – holds new data
Deleted – holds old data
Delete
Deleted – data being deleted
Inserted – is empty
The inserted and deleted tables are not indexed, so take care in querying them in the wrong way. Given the logic above, if possible it is normally cleaner to not combine an insert/update trigger. If you have to, the below if my template for doing so:
IF OBJECT_ID('tr_Example_Update', 'TR') > 0
DROP TRIGGER dbo.tr_Example_Update
GO
CREATE TRIGGER dbo.tr_Example_Update ON dbo.Test
FOR UPDATE, INSERT
AS
IF @@ROWCOUNT = 0
RETURN
SET NOCOUNT ON
IF UPDATE(Field1) OR UPDATE(Field2)
BEGIN
-- Update
IF (SELECT COUNT(1) FROM DELETED) > 0
BEGIN
PRINT 'Update logic'
END
ELSE
BEGIN
PRINT 'Insert logic'
END
END
SET NOCOUNT OFF
GO
Are triggers fired on bulk insert?
No by default, but you can turn them on: Controlling Trigger Execution When Bulk Importing Data
Tune in for Triggers Part 3: FAQ and FOP continued