'How to add username column into trigger in SQL Server for audit trail
My audit trigger,shown below, works great except that I am required to record who exactly made the change. As written the system user field was supposed to do this, but that only works if the change is made directly in the database. If it's done via the web app then the system user is the web account rather than the individual user.
Each table being modified has a UserName
column which pulls from .NET User.Identity.Name
and I'd like to add that into every row, but when I tried to access inserted.UserName
or deleted.UserName
I encountered errors.
How can I add a column to capture this information?
CREATE TRIGGER [dbo].[tr_Grading]
ON [dbo].[Grading]
FOR INSERT, UPDATE, DELETE
AS
DECLARE @bit INT ,
@field INT ,
@maxfield INT ,
@char INT ,
@fieldname VARCHAR(128) ,
@TableName VARCHAR(128) ,
@PKCols VARCHAR(1000) ,
@sql VARCHAR(2000),
@UpdateDate VARCHAR(21) ,
@SystemName VARCHAR(128) ,
@Type CHAR(1) ,
@PKSelect VARCHAR(1000)
-- change @TableName to match the table to be audited
SELECT @TableName = 'Grading'
-- date and user
SELECT @SystemName = SYSTEM_USER,
@UpdateDate = CONVERT(VARCHAR(8), GETDATE(), 112)
+ ' ' + CONVERT(VARCHAR(12), GETDATE(), 114)
-- Action
IF EXISTS (SELECT * FROM inserted)
IF EXISTS (SELECT * FROM deleted)
SELECT @Type = 'U'
ELSE
SELECT @Type = 'I'
ELSE
SELECT @Type = 'D'
-- get list of columns
SELECT * INTO #ins FROM inserted
SELECT * INTO #del FROM deleted
-- Get primary key columns for full outer join
SELECT @PKCols = COALESCE(@PKCols + ' and', ' on')
+ ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE
pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key select for insert
SELECT @PKSelect = COALESCE(@PKSelect+'+','')
+ '''<' + COLUMN_NAME
+ '=''+convert(varchar(100), coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))+''>'''
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE
pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
IF @PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, @TableName)
RETURN
END
SELECT
@field = 0,
@maxfield = MAX(ORDINAL_POSITION)
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = @TableName
WHILE @field < @maxfield
BEGIN
SELECT @field = MIN(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND ORDINAL_POSITION > @field
SELECT @bit = (@field - 1 )% 8 + 1
SELECT @bit = POWER(2,@bit - 1)
SELECT @char = ((@field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(),@char, 1) & @bit > 0 OR @Type IN ('I','D')
BEGIN
SELECT @fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND ORDINAL_POSITION = @field
SELECT @sql = '
INSERT Audit(Type, TableName, PK, FieldName, OldValue, NewValue,
UpdateDate, SystemName)
SELECT
''' + @Type + ''',
''' + @TableName + ''',
' + @PKSelect + ',''' + @fieldname + ''''
+ ',convert(varchar(1000),d.' + @fieldname + ')'
+ ',convert(varchar(1000),i.' + @fieldname + ')'
+ ',''' + @UpdateDate + ''''
+ ',''' + @SystemName + ''''
+ ' from #ins i full outer join #del d'
+ @PKCols
+ ' where i.' + @fieldname + ' <> d.' + @fieldname
+ ' or (i.' + @fieldname + ' is null and d.' + @fieldname + ' is not null)'
+ ' or (i.' + @fieldname + ' is not null and d.' + @fieldname + ' is null)'
EXEC (@sql)
END
END
GO
Solution 1:[1]
Looking at the trigger, the dynamic SQL at the end aliases inserted as I, so try I.Username
for inserted, and D.Username
for deleted in the select part.
Here's a quick explanation of what's happening in the trigger.
Selecting into temporary tables
SELECT * INTO #ins FROM inserted
SELECT * INTO #del FROM deleted
Aliasing temp tables in select statement
from #ins i full outer join #del d
EDIT - How to set username as variable
I like to set variables right at the top of the procedure/trigger like so:
CREATE TRIGGER [dbo].[tr_Grading]
ON [dbo].[Grading]
FOR INSERT, UPDATE, DELETE
AS
DECLARE @bit INT ,
@field INT ,
@maxfield INT ,
@char INT ,
@fieldname VARCHAR(128) ,
@TableName VARCHAR(128) ,
@PKCols VARCHAR(1000) ,
@sql VARCHAR(2000),
@UpdateDate VARCHAR(21) ,
@SystemName VARCHAR(128) ,
@Type CHAR(1) ,
@PKSelect VARCHAR(1000),
@UsernameI VARCHAR(1000),
@UsernameD Varchar(1000)
-- Set Username
SET @UsernameI = (SELECT Top 1 Username FROM Inserted)
SET @UsernameD = (SELECT Top 1 Username FROM Deleted)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |