25 Sep 2008
Triggers in SQL Server: how often are they called
Our dba and I had a short discussion on triggers, because I could not locate an answer to this question:
When inserting, updating or deleting more than one row in a table, is the trigger fired iteratively for each affected row, or once with all the records in the inserted and deleted tables?
To me it seemed logical that multiple rows could appear in these tables, yet I had heard from any number of people that the calls were iterative. The answer was important, since I was writing triggers to populate an audit table, and I needed to account for single rows or multiple rows in the logic.
So with the differing opinions, and not being able to get a definitive written answer, I proceeded to write a test in SQL Server 2005 Express to test the behavior. The test was easy:
First, I created a simple table called primary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[PRIMARY]( [ID] [INT] IDENTITY(1,1) NOT NULL, [Name_Value] [VARCHAR](500) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [Updated] [datetime] NOT NULL CONSTRAINT [DF_Primary_Updated] DEFAULT (getdate()), CONSTRAINT [PK_Primary] PRIMARY KEY CLUSTERED ( [ID] ASC ) ON [PRIMARY] ) ON [PRIMARY] GO SET ANSI_PADDING OFF |
Next, I created Primary_AUDIT, which wrapped extra administrative columns around the original table
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[Primary_AUDIT]( [Primary_AUDIT_ID] [BIGINT] IDENTITY(1,1) NOT NULL, [ID] [INT] NOT NULL, [Name_Value] [VARCHAR](500) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [Updated] [datetime] NOT NULL CONSTRAINT [DF_Primary_AUDIT_Updated] DEFAULT (getdate()), [AUDIT_TYPE] [VARCHAR](25) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [AUDIT_DATE] [datetime] NOT NULL, [AUDIT_GUID] [uniqueidentifier] NOT NULL, CONSTRAINT [PK_Primary_AUDIT] PRIMARY KEY CLUSTERED ( [Primary_AUDIT_ID] ASC ) ON [PRIMARY] ) ON [PRIMARY] GO SET ANSI_PADDING OFF |
The AUDIT_GUID column is important for this test. Here is the trigger I wrote for inserts (note that it uses FOR INSERT, but AFTER INSERT also has the same behavior).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TRIGGER [dbo].[tr_Primary_i] ON [dbo].[PRIMARY] FOR INSERT AS BEGIN SET NOCOUNT ON; DECLARE @dNow DATETIME SET @dNow = GETDATE() DECLARE @xGUID UNIQUEIDENTIFIER SET @xGUID = NEWID() INSERT INTO Primary_AUDIT ( ID, Name_Value, Updated, AUDIT_TYPE, AUDIT_DATE, AUDIT_GUID) SELECT ID, Name_Value, Updated, 'INSERTED', @dNow, @xGUID FROM Inserted END |
Note that both the AUDIT_DATE and AUDIT_GUID are populated with local variables, set before the insert statement, to ensure that every row is updated with the same datetime in AUDIT_DATE, and the same uniqueidentifier in AUDIT_GUID.
So here is the acid test. I load a memory table with 5 rows, and the inject the rows in table Primary using a select statement. This inserts 5 rows.
1 2 3 4 5 6 7 8 9 10 | DECLARE @p TABLE (Name_Value VARCHAR(500)) INSERT INTO @p (Name_Value) VALUES ('Test0') INSERT INTO @p (Name_Value) VALUES ('Test1') INSERT INTO @p (Name_Value) VALUES ('Test2') INSERT INTO @p (Name_Value) VALUES ('Test3') INSERT INTO @p (Name_Value) VALUES ('Test4') INSERT INTO [PRIMARY] (Name_Value) SELECT Name_Value FROM @p |
When executed, this code will show five iterations of “1 row(s) affected”, then a single iteration of “5 row(s) affected”. That last line is the first red flag that inserted has more than one row. To verify what was inserted:
1 2 3 | SELECT * FROM [PRIMARY] SELECT * FROM [Primary_AUDIT] |
Notice that in the AUDIT table, all the unique identifiers are the same. So the inserted table does have more than one row.
Keep this behavior in mind when writing triggers, especially when a trigger needs to write multiple lines for a single row (e.g. OLD and NEW rows for an update) and the information needs some kind of tag to associate the records.
Leave a Comment
You must be logged in to post a comment.