Best way to implement an audit trail in SQL Server?
Database tables
Let's say we have a Book
table whose audit log information has to be stored in a BookAuditLog
table, as illustrated by the following class diagram:
The BookAuditLog
table is created like this:
CREATE TABLE BookAuditLog (
BookId bigint NOT NULL,
OldRowData nvarchar(1000) CHECK(ISJSON(OldRowData) = 1),
NewRowData nvarchar(1000) CHECK(ISJSON(NewRowData) = 1),
DmlType varchar(10) NOT NULL CHECK (DmlType IN ('INSERT', 'UPDATE', 'DELETE')),
DmlTimestamp datetime NOT NULL,
DmlCreatedBy varchar(255) NOT NULL,
TrxTimestamp datetime NOT NULL,
PRIMARY KEY (BookId, DmlType, DmlTimestamp)
)
The BookAuditLog
table columns store the following data:
- The
BookId
column stores the identifier of the associatedBook
row that this log event was created for. - The
OldRowData
stores the JSON representation ofBook
record state prior to executing an INSERT, UPDATE, or DELETE statement. - The
NewRowData
stores the JSON representation ofBook
record state after an INSERT, UPDATE, or DELETE statement is executed. - The
DmlType
is an enumeration column that stores the DML statement type that created, updated, or deleted a givenBook
row. - The
DmlTimestamp
stores the DML statement execution timestamp. - The
DmlCreatedBy
stores the user who issued the INSERT, UPDATE, or DELETE DML statement. - The
TrxTimestamp
stores the timestamp of the transaction that changed theBook
record.
The INSERT, UPDATE and DELETE triggers
To capture the INSERT, UPDATE, and DELETE DML statements, we need to create three database triggers that are going to insert records in the BookAuditLog
table.
To intercept the INSERT statements on the Book
table, we will create the TR_Book_Insert_AuditLog
trigger:
CREATE TRIGGER TR_Book_Insert_AuditLog ON Book
FOR INSERT AS
BEGIN
DECLARE @loggedUser varchar(255)
SELECT @loggedUser = CAST(SESSION_CONTEXT(N'loggedUser') AS varchar(255))
DECLARE @transactionTimestamp datetime = SYSUTCdatetime()
INSERT INTO BookAuditLog (
BookId,
OldRowData,
NewRowData,
DmlType,
DmlTimestamp,
DmlCreatedBy,
TrxTimestamp
)
VALUES(
(SELECT id FROM Inserted),
null,
(SELECT * FROM Inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
'INSERT',
CURRENT_TIMESTAMP,
@loggedUser,
@transactionTimestamp
);
END
To capture the UPDATE statements on the Book
records, we will create the following TR_Book_Update_AuditLog
trigger:
CREATE TRIGGER TR_Book_Update_AuditLog ON Book
FOR UPDATE AS
BEGIN
DECLARE @loggedUser varchar(255)
SELECT @loggedUser = CAST(SESSION_CONTEXT(N'loggedUser') AS varchar(255))
DECLARE @transactionTimestamp datetime = SYSUTCdatetime()
INSERT INTO BookAuditLog (
BookId,
OldRowData,
NewRowData,
DmlType,
DmlTimestamp,
DmlCreatedBy,
TrxTimestamp
)
VALUES(
(SELECT id FROM Inserted),
(SELECT * FROM Deleted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
(SELECT * FROM Inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
'UPDATE',
CURRENT_TIMESTAMP,
@loggedUser,
@transactionTimestamp
);
END
To intercept the DELETE statements on the Book
table rows, we will create the following TR_Book_Delete_AuditLog
trigger:
CREATE TRIGGER TR_Book_Delete_AuditLog ON Book
FOR DELETE AS
BEGIN
DECLARE @loggedUser varchar(255)
SELECT @loggedUser = CAST(SESSION_CONTEXT(N'loggedUser') AS varchar(255))
DECLARE @transactionTimestamp datetime = SYSUTCdatetime()
INSERT INTO BookAuditLog (
BookId,
OldRowData,
NewRowData,
DmlType,
DmlTimestamp,
DmlCreatedBy,
TrxTimestamp
)
VALUES(
(SELECT id FROM Deleted),
(SELECT * FROM Deleted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
null,
'DELETE',
CURRENT_TIMESTAMP,
@loggedUser,
@transactionTimestamp
);
END
Demo time
When executing an INSERT statement on the Book
table:
INSERT INTO Book (
Author,
PriceInCents,
Publisher,
Title,
Id
)
VALUES (
'Vlad Mihalcea',
3990,
'Amazon',
'High-Performance Java Persistence 1st edition',
1
)
We can see that a record is inserted in the BookAuditLog
that captures the INSERT statement that was just executed on the Book
table:
| BookId | OldRowData | NewRowData | DmlType | DmlTimestamp | DmlCreatedBy | TrxTimestamp |
|--------|------------|------------------------------------------------------------------------------------------------------------------------------------|---------|-------------------------|---------------|-------------------------|
| 1 | | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | INSERT | 2020-11-08 08:40:28.343 | Vlad Mihalcea | 2020-11-08 06:40:28.347 |
When updating the Book
table row:
UPDATE Book
SET PriceInCents = 4499
WHERE Id = 1
We can see that a new record is going to be added to the BookAuditLog
by the AFTER UPDATE trigger on the Book
table:
| BookId | OldRowData | NewRowData | DmlType | DmlTimestamp | DmlCreatedBy | TrxTimestamp |
|--------|------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|---------|-------------------------|---------------|-------------------------|
| 1 | | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | INSERT | 2020-11-08 08:40:28.343 | Vlad Mihalcea | 2020-11-08 06:40:28.347 |
| 1 | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":4499,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | UPDATE | 2020-11-08 08:43:22.803 | Vlad Mihalcea | 2020-11-08 06:43:22.807 |
When deleting the Book
table row:
DELETE FROM Book
WHERE Id = 1
A new record is added to the BookAuditLog
by the AFTER DELETE trigger on the Book
table:
| BookId | OldRowData | NewRowData | DmlType | DmlTimestamp | DmlCreatedBy | TrxTimestamp |
|--------|------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|---------|-------------------------|---------------|-------------------------|
| 1 | | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | INSERT | 2020-11-08 08:40:28.343 | Vlad Mihalcea | 2020-11-08 06:40:28.347 |
| 1 | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":4499,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | UPDATE | 2020-11-08 08:43:22.803 | Vlad Mihalcea | 2020-11-08 06:43:22.807 |
| 1 | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":4499,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | | DELETE | 2020-11-08 08:44:25.630 | Vlad Mihalcea | 2020-11-08 06:44:25.633 |
There are many ways to do that; it depends which version of SQL Server you are using.
Here are few
Audit trail with shadow table and trigger Here is the link
Also you can consider to use SQL Server 2008 Audit feature Here is the link