I don't know if these requirements are standard or not but I'm wondering is there a solution out there which can do the following:
I rather not have to code this for every table. I'm wondering if there is a solution you can install on top of SQL Server which will do this for you?
To generate audit trails in shadow tables, the safest option is to create insert, update and delete triggers, that for each affected record in the original table generate a record in the audit table. The triggers should have access to all the audit information you need to record in the shadow table.
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
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:
BookId
column stores the identifier of the associated Book
row that this log event was created for.OldRowData
stores the JSON representation of Book
record state prior to executing an INSERT, UPDATE, or DELETE statement.NewRowData
stores the JSON representation of Book
record state after an INSERT, UPDATE, or DELETE statement is executed.DmlType
is an enumeration column that stores the DML statement type that created, updated, or deleted a given Book
row.DmlTimestamp
stores the DML statement execution timestamp.DmlCreatedBy
stores the user who issued the INSERT, UPDATE, or DELETE DML statement.TrxTimestamp
stores the timestamp of the transaction that changed the Book
record.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
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 |
I create trigger which does it for XML this way we can log all tables to same table, making it more flexible
CREATE TABLE [dbo].[AuditAll] (
AuditId int NOT NULL IDENTITY(1,1),
[DateTime] datetime NOT NULL,
TableName nvarchar(255) NOT NULL,
AuditEntry xml NULL,
CONSTRAINT [PK_AuditAll] PRIMARY KEY CLUSTERED ( AuditId ASC )
)
I needed only 'old' values, so I store deleted table only, inserted table can be seen in the table anyhow.
CREATE TRIGGER AuditSimple
ON Simple
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF (SELECT COUNT(*) FROM deleted) > 0
begin
Declare @AuditMessage XML
--set valut to all xml from deleted table
set @AuditMessage = (select * from deleted for xml auto)
insert into AuditAll( DateTime, TableName, AuditEntry )
values ( GetDate(), 'Simple', @AuditMessage )
end
END
GO
I guess this could easily be called in sp_foreach to create it for each table in datatabase but we did not needed it at the moment, just remember to change your table names
cheers
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With