patternsqlModerate
Passing info on who deleted record onto a Delete trigger
Viewed 0 times
infotriggerdeletepassingwhorecordontodeleted
Problem
In setting up an audit trail I have no problem tracking who is updating or inserting records in a table, however, tracking who deletes records seems more problematic.
I can track Inserts/Updates by including in the Insert/Update the field "UpdatedBy". This allows the INSERT/UPDATE trigger to have access to the field "UpdatedBy" via
Here is an Insert/Update trigger
Using SQL Server 2012
I can track Inserts/Updates by including in the Insert/Update the field "UpdatedBy". This allows the INSERT/UPDATE trigger to have access to the field "UpdatedBy" via
inserted.UpdatedBy. However, with the Delete trigger no data is inserted/updated. Is there a way to pass information onto the Delete trigger such that it could know who deleted the record?Here is an Insert/Update trigger
ALTER TRIGGER [dbo].[trg_MyTable_InsertUpdate]
ON [dbo].[MyTable]
FOR INSERT, UPDATE
AS
INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges)
VALUES (inserted.ID, inserted.LastUpdatedBy)
FROM insertedUsing SQL Server 2012
Solution
Is there a way to pass information onto the Delete trigger such that it could know who deleted the record?
Yes: by using a very cool (and under utilized feature) called
-
Context info is a VARBINARY(128)
-
Set via: SET CONTEXT_INFO
-
Get via: CONTEXT_INFO()
Test with the following to see how it works. Notice that I am converting to
Results:
PUTTING IT ALL TOGETHER:
-
The app should call a "Delete" stored procedure that passes in the UserName (or whatever) that is deleting the record. I assume this is already the model being used since it sounds like you are already tracking Insert and Update operations.
-
The "Delete" stored procedure does:
-
The audit trigger does:
-
Please note that, as @SeanGallardy pointed out in a comment, due to other procedures and/or ad hoc queries deleting records from this table, it is possible that either:
-
For this reason I have updated the above
-
For this reason I added a
UPDATE FOR SQL SERVER 2016 AND NEWER
SQL Server 2016 added an improved version of this per-session memory: Session Context. The new Session Context is essentially a hash table of Key-Value pairs with the "Key" being of type
For details, please see the following documentation pages:
Yes: by using a very cool (and under utilized feature) called
CONTEXT_INFO. It is essentially session memory that exists in all scopes and is not bound by transactions. It can be used to pass info (any info--well, any that fits into the limited space) to triggers as well as back and forth between sub-proc / EXEC calls. And I have used it before for this exact same situation.-
Context info is a VARBINARY(128)
-
Set via: SET CONTEXT_INFO
-
Get via: CONTEXT_INFO()
Test with the following to see how it works. Notice that I am converting to
CHAR(128) before the CONVERT(VARBINARY(128), ... This is to force blank-padding to make it easier to convert back to VARCHAR when getting it out of CONTEXT_INFO() since VARBINARY(128) is right-padded with 0x00s.SELECT CONTEXT_INFO();
-- Initially = NULL
DECLARE @EncodedUser VARBINARY(128);
SET @EncodedUser = CONVERT(VARBINARY(128),
CONVERT(CHAR(128), 'I deleted ALL your records! HA HA!')
);
SET CONTEXT_INFO @EncodedUser;
SELECT CONTEXT_INFO() AS [RawContextInfo],
RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())) AS [DecodedUser];
Results:
0x492064656C6574656420414C4C20796F7572207265636F7264732120484120484121202020202020...
I deleted ALL your records! HA HA!PUTTING IT ALL TOGETHER:
-
The app should call a "Delete" stored procedure that passes in the UserName (or whatever) that is deleting the record. I assume this is already the model being used since it sounds like you are already tracking Insert and Update operations.
-
The "Delete" stored procedure does:
DECLARE @EncodedUser VARBINARY(128);
SET @EncodedUser = CONVERT(VARBINARY(128),
CONVERT(CHAR(128), @UserName)
);
SET CONTEXT_INFO @EncodedUser;
-- DELETE STUFF HERE-
The audit trigger does:
-- Set the INT value in LEFT (currently 50) to the max size of [UserWhoMadeChanges]
INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges)
SELECT del.ID, COALESCE(
LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50),
'')
FROM DELETED del;-
Please note that, as @SeanGallardy pointed out in a comment, due to other procedures and/or ad hoc queries deleting records from this table, it is possible that either:
-
CONTEXT_INFO has not been set and is still NULL: For this reason I have updated the above
INSERT INTO AuditTable to use a COALESCE to default the value. Or, if you don't want a default and require a name, then you could do something similar to:DECLARE @UserName VARCHAR(50); -- set to the size of AuditTable.[UserWhoMadeChanges]
SET @UserName = LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50);
IF (@UserName IS NULL)
BEGIN
ROLLBACK TRAN; -- cancel the DELETE operation
RAISERROR('Please set UserName via "SET CONTEXT_INFO.." and try again.', 16 ,1);
END;
-- use @UserName in the INSERT...SELECT-
CONTEXT_INFO has been set to a value that is not a valid UserName, and hence might exceed the size of the AuditTable.[UserWhoMadeChanges] field: For this reason I added a
LEFT function to ensure that whatever is grabbed out of CONTEXT_INFO will not break the INSERT. As noted in the code, you just need to set the 50 to the actual size of the UserWhoMadeChanges field.UPDATE FOR SQL SERVER 2016 AND NEWER
SQL Server 2016 added an improved version of this per-session memory: Session Context. The new Session Context is essentially a hash table of Key-Value pairs with the "Key" being of type
sysname (i.e. NVARCHAR(128) ) and the "Value" being SQL_VARIANT. Meaning:- There is now a separation of values so less likely to conflict with other uses
- You can store various types, no longer needing to worry about the odd behavior when getting the value back out via
CONTEXT_INFO()(for details, please see my post: Why Doesn’t CONTEXT_INFO() Return the Exact Value Set by SET CONTEXT_INFO?)
- You get a lot more space: 8000 bytes max per "Value", up to 256kb total across all keys (compared to the 128 bytes max of
CONTEXT_INFO)
For details, please see the following documentation pages:
- sp_set_session_context
- SESSION_CONTEXT
Code Snippets
0x492064656C6574656420414C4C20796F7572207265636F7264732120484120484121202020202020...
I deleted ALL your records! HA HA!DECLARE @EncodedUser VARBINARY(128);
SET @EncodedUser = CONVERT(VARBINARY(128),
CONVERT(CHAR(128), @UserName)
);
SET CONTEXT_INFO @EncodedUser;
-- DELETE STUFF HERE-- Set the INT value in LEFT (currently 50) to the max size of [UserWhoMadeChanges]
INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges)
SELECT del.ID, COALESCE(
LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50),
'<unknown>')
FROM DELETED del;DECLARE @UserName VARCHAR(50); -- set to the size of AuditTable.[UserWhoMadeChanges]
SET @UserName = LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50);
IF (@UserName IS NULL)
BEGIN
ROLLBACK TRAN; -- cancel the DELETE operation
RAISERROR('Please set UserName via "SET CONTEXT_INFO.." and try again.', 16 ,1);
END;
-- use @UserName in the INSERT...SELECTContext
StackExchange Database Administrators Q#82615, answer score: 13
Revisions (0)
No revisions yet.