Last year, I was assigned task to build our own Data Audit
mechanism in SQL Server 2005. To accomplish this task, there were number of
articles and scripts on the internet. I planned to get the best out of those
articles and scripts and created my own Trigger + Service Broker Based
solution.
mechanism in SQL Server 2005. To accomplish this task, there were number of
articles and scripts on the internet. I planned to get the best out of those
articles and scripts and created my own Trigger + Service Broker Based
solution.
Trigger based audit solution? Sounds like a lot of
performance issues. But I used service broker in the triggers to get the things
done.
performance issues. But I used service broker in the triggers to get the things
done.
Core Logic
The core logic is;
1. Create New Audit Database
2. Activate Service Broker on New Audit Database and your own database
3. Build tables for Audit. It can be all tables as well.2. Activate Service Broker on New Audit Database and your own database
4. Ruin script to add trigger on the fly on the specified tables.
5. Enjoy Audit reports from Audit Database.
I will discuss each point in detail.
1. Create New Audit Database
This database will hold the New and old values of the columns changed on the
production database. Create new database as AuditDatabase.
It will have the following Tables;
production database. Create new database as AuditDatabase.
It will have the following Tables;
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[tblAud](
[tID] [int] IDENTITY(1,1) NOT NULL,
[xmlQuery] [xml] NULL,
[Upd_Det] [bit],
[tDate] [datetime] NULL CONSTRAINT [DF_tblAud_tDate] DEFAULT (getdate()),
[ColU] [varbinary](128) NULL,
CONSTRAINT
[PK_tblAud] PRIMARY KEY CLUSTERED
[PK_tblAud] PRIMARY KEY CLUSTERED
(
[tID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING
OFF
tblAud will hold the XML of the changed record in xmlQuery. Column upd_Det bit column will tell us if the XML is parsed or not. Column tDate will tell us the date
when the record was initialized.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tblAudDetail](
[AudDetID] [int] IDENTITY(1,1) NOT NULL,
[tid] [int] NULL,
[dbName] [nvarchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[TABLENAME] [nvarchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[FieldName] [nvarchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Priorval] [nvarchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[CurrVal] [nvarchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
CONSTRAINT
[PK_tblAudDetail] PRIMARY KEY CLUSTERED
[PK_tblAudDetail] PRIMARY KEY CLUSTERED
(
[AudDetID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
tblAudDetail will hold the details of changed information from XML in tblAud.
You can see from the schema of the table that it has columns dbName. It means we can use one Audit Database for multiple Production databases.
tblErrorlog will hold the details of errors in messages so that the XML can be debugged.
USE
[AuditDatabase]
[AuditDatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tblErrorLog](
[ErrorNumber] [int] NOT NULL,
[ErrorSeverity] [int] NULL,
[ErrorState] [int] NULL,
[ErrorLine] [int] NULL,
[ErrorProcedure] [nvarchar](126) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[ErrorMessage] [nvarchar](4000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[ErrorTime] [datetime] NULL CONSTRAINT [DF__tblErrorL__Error__4865BE2A] DEFAULT (getdate()),
[DbUserName] [sysname] COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[sysUserName] [sysname] COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]
Audit Database will also have the three stored procedures.
The following stored procedure will be activated by the service broker queue
when any message from the production database is sent;
The following stored procedure will be activated by the service broker queue
when any message from the production database is sent;
USE
[AuditDatabase]
[AuditDatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[uspInsertAuditRec]
AS
BEGIN
SET NOCOUNT ON;
Begin Try
Declare @ConversationHandle as uniqueidentifier
Declare @MessageBody as varbinary(max)
Declare @MessageXML as XML
Declare @MessageType as sysname
Declare @pBQuery nvarchar(max)
Begin TRANSACTION
Print 'Started Receiving';
WAITFOR(
RECEIVE top (1)
@MessageType = message_type_name,
@ConversationHandle = conversation_handle,
@ConversationHandle = conversation_handle,
@MessageBody = message_body
FROM BLOB_AdtQueue_Remote1
), TIMEOUT 1000;
Select @pBQuery = convert(nvarchar(max),@MessageBody)
Select @MessageXML = cast(@pBQuery as XML)
Declare @sID as INT
Declare @DBName as nvarchar(100)
Declare @TableName as nvarchar(100)
IF @MessageType = 'BLOB'
BEGIN
select @DBName = x.header.value('(//SourceDb)[1]', 'nvarchar(50)') ,
@TableName = x.header.value('(//SourceTable)[1]', 'nvarchar(50)')
FROM @MessageXML.nodes('//AuditMsg') AS x(header)
Insert into tblAud (xmlQuery,upd_det) values (cast(@pBQuery as XML),0)
Select @sID = Scope_identity()
END
END
ELSE
BEGIN
INSERT INTO tblAud (xmlQuery,upd_det) values ('<a>aaa<a/>','<a>aaa<a/>')
END
END CONVERSATION @ConversationHandle
COMMIT
End Try
Begin Catch
Rollback
print 'In Catch...'
INSERT INTO DBO.tblErrorLog
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_LINE() AS ErrorLine,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_MESSAGE() AS ErrorMessage,GETDATE(),
CONVERT(sysname ,USER_NAME())AS DBUSERNAME,
CONVERT(SYSNAME,SUSER_SNAME()) AS SYSUSERNAME
Insert into dbo.tblErrorXML
Select @MessageBody
End Catch
END
The following stored procedures will parse the XML and will mark the record in tblAud;
USE
[AuditDatabase]
[AuditDatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
--
=============================================
=============================================
-- Author: Atif Sheikh
-- Create date: 24-May-2010
-- Description: Update Detail
--
=============================================
=============================================
CREATE PROCEDURE [dbo].[uspUpdateDetail]
AS
BEGIN
SET NOCOUNT ON;
Declare @tid int
Declare @DbName varchar(100)
Declare @TableName varchar(100)
Declare d1 Cursor for Select tid from tblAud where upd_det = 0
Open d1
Fetch Next from d1 into @tid
while @@Fetch_Status = 0
begin
PRINT @tid select @DbName = xmlQuery.value('(//SourceDb)[1]', 'varchar(50)') ,
@TableName = xmlQuery.value('(//SourceTable)[1]', 'varchar(50)')
FROM tblAud where tid = @tid
if @DbName is Not Null and @TableName is Not Null
exec uspUpdateAuditDetailFromAuditTableID @tid,@DbName,@TableName
Fetch Next from d1 into @tid
end
Deallocate d1
END
USE
[AuditDatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
--
=============================================
=============================================
-- Author: Atif-ullah Sheikh
-- Create date: 24-May-2008
-- Description: Insert into tblAudDetail
--
=============================================
=============================================
CREATE PROCEDURE [dbo].[uspUpdateAuditDetailFromAuditTableID]
(
@pID int,
@pDBName nvarchar(100),
@pTableName nvarchar(100)
)
AS
BEGIN
Begin Try
Begin Tran
Declare @ColName nvarchar(max)
Declare @Chk1 nvarchar(max)
Declare @Chk2 nvarchar(max)
SET NOCOUNT ON;
Declare @sSql varchar(max)
Declare @SQL1 nvarchar(max)
Declare @SQL nvarchar(max)
Declare @pDef nvarchar(max)
Declare @sSql2 varchar(max)
Declare @SelectCols varchar(max)
Declare @SelectCols2 varchar(max)
Declare @ObjId int
Set @sql1 = N'Select @ObjId = Object_ID
from ' + @pDBName + '.sys.objects where name = ''' + @pTableName + ''''
from ' + @pDBName + '.sys.objects where name = ''' + @pTableName + ''''
Set @pDef = N'@ObjId varchar(100) OUTPUT'
Exec sp_executesql @sql1 ,@pDef,@ObjId=@ObjId OUTPUT
Set @sql = 'Declare col2 Cursor for
Select name from ' + @pDBName + '.sys.columns where
Object_Id = '+ cast(@ObjID as varchar(10))+ ' and system_type_id not
in (34,35,99,241,165,173)'
Select name from ' + @pDBName + '.sys.columns where
Object_Id = '+ cast(@ObjID as varchar(10))+ ' and system_type_id not
in (34,35,99,241,165,173)'
print(@sql)
Exec (@sql)
Open col2
Declare @XMLvar XML
Select @XMLvar = xmlQuery from tblAud where tid = @pID
Set @sql = ''
Declare @SQL2 varchar(max)
Declare @SQL3 varchar(max)
Declare @SQL4 varchar(max)
Set @sql2 = ''
Set @sql3 = ''
Set @sql4 = ''
Declare @cname varchar(100)
Fetch next From Col2 into @cname
Set @sql3 = '(Order By x.header.value(''@' + @cname + ''',''varchar(100)'')) '
Set @sql = 'Declare @XMLvar XML
Select @XMLvar = xmlQuery from tblAud where tid = ' + cast(@pID as varchar(10))+ ' Select * into
##ctbl from ( Select Row_Number() Over '
##ctbl from ( Select Row_Number() Over '
while @@Fetch_Status = 0
begin
if @Sql2 <> '' Set @Sql2 = @Sql2 + ','
Set @Sql2 = @Sql2 + 'x.header.value(''@' + @cname + ''',
''varchar(50)'') ' + @cname + '_d'
''varchar(50)'') ' + @cname + '_d'
if @Sql4 <> ''
Set @Sql4 = @Sql4 + ',' Set @Sql4 = @Sql4 + 'x.header.value(''@' + @cname + ''',
''varchar(50)'') ' + @cname + '_i'
''varchar(50)'') ' + @cname + '_i'
Fetch next From Col2 into @cname
end
Set @sql = @sql + @sql3 + ' d_rid,' + @sql2
Set @sql = @sql + ' FROM @XMLvar.nodes(''//ChangedData/t'') AS x(header)) xx
Left
OUter join (
OUter join (
select Row_number() over ' + @sql3 + ' i_rid,' + @sql4 + '
FROM @XMLvar.nodes(''//NewRec/t'') AS x(header))yy
on yy.i_rid = xx.d_rid '
print(@sql)
Deallocate Col2
Exec (@sql)
Declare @dval varchar(100)
Declare @ival varchar(100)
Declare @rid int
Set @sql = 'Declare col2 Cursor SCROLL
for Select name from ' + @pDBName + '.sys.columns where
Object_Id = '+ cast(@ObjID as varchar(10))+ ' and system_type_id not
in (34,35,99,241,165,173)'
for Select name from ' + @pDBName + '.sys.columns where
Object_Id = '+ cast(@ObjID as varchar(10))+ ' and system_type_id not
in (34,35,99,241,165,173)'
Exec (@sql)
Open col2
Declare @vRID int
Declare Col3 Cursor for Select Case when i_RID is Null then d_RID else i_RID end from ##ctbl
Open Col3
Fetch Next from col3 into @rid
while @@Fetch_Status = 0
begin
Fetch next From Col2 into @cname
While @@Fetch_Status = 0
begin
Set @sql1 = ''
Set @sql1 = @sql1 + N' Select @dval1 = '+ @cname + '_d ,@ival1 =
'+ @cname +'_i from ##ctbl where case when i_RID is Null then d_rid else
i_rid end = ' + cast(@rid as varchar(10))
'+ @cname +'_i from ##ctbl where case when i_RID is Null then d_rid else
i_rid end = ' + cast(@rid as varchar(10))
Set @pDef = N'@dval1 varchar(100) OUTPUT, @ival1 varchar(100) OUTPUT'
Set @vRID = 0
Select @vRID = i_rid from ##ctbl where case when i_RID is Null then d_rid else i_rid end = @rid
Exec sp_executesql @sql1 ,@pDef,@dval1=@dval OUTPUT,@ival1=@ival OUTPUT
if (IsNull(@dval,'') <> IsNull(@ival,'')) or @cname Like '%ID'
begin
Insert into
tblAudDetail (tid,DBName,TableName,FieldName,PriorVal,CurrVal)
Values
(@pID,@pDBName,@pTableName,@cname,case when IsNull(@vRID,0) <> 0 then @dval else @ival end,case when IsNull(@vRID,0) <> 0 then @ival
else @dval end)
Values
(@pID,@pDBName,@pTableName,@cname,case when IsNull(@vRID,0) <> 0 then @dval else @ival end,case when IsNull(@vRID,0) <> 0 then @ival
else @dval end)
end
Fetch next From Col2 into @cname
end
Fetch FIRST From Col2 into @cname
Fetch PRIOR From Col2 into @cname
Fetch Next from col3 into @rid
end
Deallocate Col2
Deallocate Col3
Drop table ##ctbl
update tblAud set upd_det = 1 where tid = @pID
Commit Tran
End Try
begin Catch
Rollback
INSERT INTO dbo.tblErrorLog
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_LINE() AS ErrorLine,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_MESSAGE() AS ErrorMessage,GETDATE(),
CONVERT(sysname ,USER_NAME())AS DBUSERNAME,
CONVERT(SYSNAME,SUSER_SNAME()) AS SYSUSERNAME
end catch
END
2. Activate Service Broker on New Audit Database and your own database
Next step is to activate service broker on both databases.
ALTER DATABASE MyProductionDB
SET ENABLE_BROKER
WITH ROLLBACK IMMEDIATE
SET ENABLE_BROKER
WITH ROLLBACK IMMEDIATE
GO
ALTER DATABASE AuditDatabase
SET ENABLE_BROKER
WITH ROLLBACK IMMEDIATEGO
Run the following script to create service broker objects in MyProductionDB. First the master key is created. According to MSDN, “The database master key is a symmetric key used to protect the private keys of certificates and asymmetric keys that are present in the database.”
use
MyProductionDB
MyProductionDB
drop master key
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'onteuhoeu'
GO
A message type defines the name of a message and the validation that Service Broker performs on messages that have that name. Both sides of a conversation must define the same message types.
CREATE MESSAGE TYPE BLOB VALIDATION = NONE
GO
A contract defines the message types that are used in a Service Broker conversation and also determines which side of the conversation can send messages of that type. Each conversation follows a contract. The initiating service specifies the contract for the conversation when the conversation starts. The target service specifies the contracts that the target service accepts conversations for.
CREATE CONTRACT BLOB_Contract
(BLOB SENT BY ANY)GO
Queues store messages. When a message arrives for a service, Service Broker puts the message on the queue associated with the service.
CREATE QUEUE BLOB_Queue_Init
GO
A Service Broker service is a name for a specific task or set of tasks. Service Broker uses the name of the service to route messages, deliver messages to the correct queue within a database, and enforce the contract for a conversation.
CREATE SERVICE BLOB_Service_Init
ON QUEUE BLOB_Queue_Init
(BLOB_Contract)
Run the following script to create service broker objects in AuditDatabase. Same types of objects as above are created on AuditDatabase. But with different names;
Use
AuditDatabase
AuditDatabase
GO
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'onteuhoeu'
GO
CREATE MESSAGE TYPE BLOB
VALIDATION = NONE
GO
create CONTRACT BLOB_Contract
(BLOB SENT BY ANY)
(BLOB SENT BY ANY)
GO
CREATE QUEUE BLOB_AdtQueue_Remote1
GO
CREATE SERVICE BLOB_AdtSrv_Remote1
ON QUEUE BLOB_AdtQueue_Remote1(BLOB_Contract)
GO
We need to alter the queue BLOB_AdtQueue_Remote1 to set its status to ON and associate procedure to be executed when a message is inserted in a queue. MAX_QUEUE_READERS specifies the maximum number of instances of the activation stored procedure that the queue starts simultaneously. The value of max_readers must be a number between 0 and 32767.
ALTER QUEUE BLOB_AdtQueue_Remote1
WITH ACTIVATION
(
STATUS = ON,
PROCEDURE_NAME = dbo.uspInsertAuditRec ,
MAX_QUEUE_READERS = 1,
EXECUTE AS OWNER
)
GO
The security principal that owns the initiating service must have SEND permission on the target service. So we need to execute,
GRANT SEND ON SERVICE::BLOB_Service_Remote1 TO [Public];
GO
3. Build tables for Audit. It can be all tables as well.
Next we will create stored procedures to apply Triggers on the tables we need to Audit.
Run the following script on Production Database to create stored procedure to add trigger on the fly;
Use
MyProductionDB
MyProductionDB
go
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
-- =============================================
-- Author: Atif Sheikh
-- Create date: 28-May-2010
-- Description: Create Audit Trigger
--
=============================================
=============================================
CREATE PROCEDURE [dbo].[uspCreateTriggerToAudit]
@TABLENAME varchar(100)
AS
BEGIN
SET NOCOUNT ON;
Declare @SCHEMA varchar(100)
Declare @colnames varchar(max)
Declare @objid int
Declare @sql varchar(max)
Select @objid = object_id from sys.objects where name = @tableName
Select @TABLENAME = table_name, @Schema
= Table_Schema from information_schema.tables where table_name = @tableName
= Table_Schema from information_schema.tables where table_name = @tableName
Select @colnames = COALESCE(@colnames+', ','')+ '['+ sys.columns.name +']'
from sys.columns where object_id = @objid
from sys.columns where object_id = @objid
and system_type_id not in (34,35,99,241,165,173)
SET @SQL='CREATE TRIGGER ['+@TABLENAME+'_AuditTrigger]
ON ['+@SCHEMA+'].['+@TABLENAME+']
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @auditBody varchar(max)
DECLARE @auditBody1 varchar(max)
DECLARE @AppUserID varchar(max)
DECLARE @Objectid bigint
DECLARE @DMLType CHAR(1)
Set @auditBody1 = ''''
Set @auditBody = ''''
Set @AppUserID = ''''
Select @Objectid = parent_object_id
from sys.objects where name = ''' + @tableName + '_AuditTrigger''
if Exists(Select column_id from
sys.columns where object_id = @Objectid and name = ''UpdatedBy'')
sys.columns where object_id = @Objectid and name = ''UpdatedBy'')
begin
--Select @AppUserID = UpdatedBy from inserted as ii
Set @AppUserID = ''''
end
IF NOT EXISTS (SELECT * FROM inserted)
BEGIN
SELECT @auditBody = (select '+ @colnames +' FROM deleted AS t FOR XML AUTO)
Select @DMLType = ''D''
END
-- after update or insert statement
ELSE
BEGIN
IF EXISTS (SELECT * FROM
deleted)
deleted)
begin
SELECT @auditBody = (select '+ @colnames +' FROM deleted AS t FOR XML
AUTO)
AUTO)
SELECT @auditBody1 = (select '+ @colnames +' FROM Inserted AS t FOR XML
AUTO)
AUTO)
SELECT @DMLType = ''U''
end
ELSE
begin
SELECT @auditBody = (select '+ @colnames +' FROM inserted AS t FOR XML
AUTO)
AUTO)
SELECT @DMLType = ''I''
end
END
SELECT @auditBody =
''<AuditMsg>
<SourceDb>'' +
DB_NAME() + ''</SourceDb>
DB_NAME() + ''</SourceDb>
<SourceTable>' + @tableName + '</SourceTable>
<UserId>'' +
SUSER_SNAME() + ''</UserId>
SUSER_SNAME() + ''</UserId>
<AppUserId>'' +
@AppUserID + ''</AppUserId>
@AppUserID + ''</AppUserId>
<DMLType>'' +
@DMLType + ''</DMLType>
@DMLType + ''</DMLType>
<ChangedData>'' +
CAST(@auditBody AS NVARCHAR(MAX)) + ''</ChangedData>''
CAST(@auditBody AS NVARCHAR(MAX)) + ''</ChangedData>''
if @DMLType = ''U''
SELECT @auditBody =
@auditBody + ''<NewRec>'' + @auditBody1 + ''</NewRec>''
@auditBody + ''<NewRec>'' + @auditBody1 + ''</NewRec>''
SELECT @auditBody = @auditBody +
''</AuditMsg>''
''</AuditMsg>''
if @auditBody <> ''''
begin
DECLARE @h UNIQUEIDENTIFIER
Declare @CXml varchar(max)
Set @CXml = @auditBody
BEGIN DIALOG CONVERSATION @h
FROM SERVICE BLOB_Service_Init
TO SERVICE
''BLOB_AdtSrv_Remote1''
''BLOB_AdtSrv_Remote1''
ON CONTRACT BLOB_Contract
WITH ENCRYPTION=OFF ;
SEND ON CONVERSATION @h
MESSAGE TYPE BLOB
(CONVERT(VARBINARY(max), @CXml))
(CONVERT(VARBINARY(max), @CXml))
end
END'
print @SQL
set ANSI_NULLS
ON
ON
set QUOTED_IDENTIFIER
ON
ON
exec (@SQL)
END
The following stored procedure on production database takes
the comma separated list of object_id of tables we
want to audit on Production database.
the comma separated list of object_id of tables we
want to audit on Production database.
Use MyProductionDB
go CREATE PROCEDURE [dbo].[uspGetAuditTables]
@pObjectID varchar(max) ,
@pAlias varchar(max) = NULL
AS
AS
BEGIN
SET NOCOUNT ON;Declare @val varchar(1000)
Declare @val2 varchar(1000)
Declare @tab as Table (ObjectID bigint,
Alias nvarchar(1000))
Alias nvarchar(1000))
Insert into @tab
Select * from tblAuditTablesdelete from tblAuditTables
insert into tblAuditTables (ObjectID,Alias)
select a.value,b.value from dbo.fnSplit(@pObjectID,',') a, dbo.fnSplit(@pAlias,',') b where a.tid = b.tid
Declare C1 Cursor For select name from dbo.fnSplit(@pObjectID,',') inner join sys.objects on sys.objects.Object_id = value
Open C1
Fetch Next from C1 into @val
while @@Fetch_Status = 0
begin print(@val)
if not Exists(Select object_id from sys.objects where name = rtrim(ltrim(@val)) + '_AuditTrigger' and Type = 'TR')
begin
Set @val = rtrim(ltrim(@val))
exec uspCreateTriggerToAudit @val
end
Fetch Next from C1 into @val
end
end
Close C1
Deallocate C1Declare C1 Cursor For
Select sys.objects.name,sys.schemas.name from sys.objects inner join sys.schemas on sys.schemas.schema_id = sys.objects.schema_id where Object_id in
(Select objectid from @tab
Except
Select objectid from tblAuditTables)
Declare @sSql varchar(1000)
Open C1
Fetch Next from C1 into @val,@val2
while @@Fetch_Status = 0
begin Set @sSql = 'Drop Trigger [' + @val2 + '].[' + @val + '_AuditTrigger]'
Exec (@sSql)
Fetch Next from C1 into @val,@val2
end
end
Close C1
Deallocate C1
END
The following table has to be created on production database;
SET ANSI_NULLS ON
GOSET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tblAuditTables](
[ObjectID] [int] NOT NULL,
[Alias] [nvarchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
CONSTRAINT [PK_tblAuditTables] PRIMARY KEY CLUSTERED
(
[ObjectID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
Once you have created a comma separated list of tables you want to audit on your production database, you can run the following script;
exec [dbo].[uspGetAuditTables] '159196,1591244’,’MyTable1,
MyTable2’
This will create the trigger on MyTable1 and MyTable2.
4. Enjoy Audit reports from Audit Database.
Now change any value in MyTable1 or MyTable2 and it will be
reflected in tblAud as XML in xmlQuery
column. You can create reports on Audit data from Audit database according to
your requirements. I have created the Insert / Update /Delete statements of the
production database from the AuditDatabase.
reflected in tblAud as XML in xmlQuery
column. You can create reports on Audit data from Audit database according to
your requirements. I have created the Insert / Update /Delete statements of the
production database from the AuditDatabase.
This long solution might have some drawbacks. I am currently using
it on one of my systems in QA without any error (till now). And we are planning
to ship it to production server. Any comments / suggestions are most welcome.
it on one of my systems in QA without any error (till now). And we are planning
to ship it to production server. Any comments / suggestions are most welcome.
No comments:
Post a Comment