Showing posts with label audit. Show all posts
Showing posts with label audit. Show all posts

Monday, March 26, 2012

Generic Audit Trigger CLR C#(Works when the trigger is attached to any Table)

This Audit Trigger is Generic (i.e. non-"Table Specific") attach it to any tabel and it should work. Be sure and create the 'Audit' table first though.

The following code write audit entries to a Table called
'Audit'
with columns
'ActionType' //varchar
'TableName' //varchar
'PK' //varchar
'FieldName' //varchar
'OldValue' //varchar
'NewValue' //varchar
'ChangeDateTime' //datetime
'ChangeBy' //varchar

using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;

public partial class Triggers
{
//A Generic Trigger for Insert, Update and Delete Actions on any Table
[Microsoft.SqlServer.Server.SqlTrigger(Name = "AuditTrigger", Event = "FOR INSERT, UPDATE, DELETE")]

public static void AuditTrigger()
{
SqlTriggerContext tcontext = SqlContext.TriggerContext; //Trigger Context
string TName; //Where we store the Altered Table's Name
string User; //Where we will store the Database Username
DataRow iRow; //DataRow to hold the inserted values
DataRow dRow; //DataRow to how the deleted/overwritten values
DataRow aRow; //Audit DataRow to build our Audit entry with
string PKString; //Will temporarily store the Primary Key Column Names and Values here
using (SqlConnection conn = new SqlConnection("context connection=true"))//Our Connection
{
conn.Open();//Open the Connection
//Build the AuditAdapter and Mathcing Table
SqlDataAdapter AuditAdapter = new SqlDataAdapter("SELECT * FROM Audit WHERE 1=0", conn);
DataTable AuditTable = new DataTable();
AuditAdapter.FillSchema(AuditTable, SchemaType.Source);
SqlCommandBuilder AuditCommandBuilder = new SqlCommandBuilder(AuditAdapter);//Populates the Insert command for us
//Get the inserted values
SqlDataAdapter Loader = new SqlDataAdapter("SELECT * from INSERTED", conn);
DataTable inserted = new DataTable();
Loader.Fill(inserted);
//Get the deleted and/or overwritten values
Loader.SelectCommand.CommandText = "SELECT * from DELETED";
DataTable deleted = new DataTable();
Loader.Fill(deleted);
//Retrieve the Name of the Table that currently has a lock from the executing command(i.e. the one that caused this trigger to fire)
SqlCommand cmd = new SqlCommand("SELECT object_name(resource_associated_entity_id) FROM
ys.dm_tran_locks WHERE request_session_id = @.@.spid and resource_type = 'OBJECT'", conn);
TName = cmd.ExecuteScalar().ToString();
//Retrieve the UserName of the current Database User
SqlCommand curUserCommand = new SqlCommand("SELECT system_user", conn);
User = curUserCommand.ExecuteScalar().ToString();
//Adapted the following command from a T-SQL audit trigger by Nigel Rivett
//http://www.nigelrivett.net/AuditTrailTrigger.html
SqlDataAdapter PKTableAdapter = new SqlDataAdapter(@."SELECT c.COLUMN_NAME
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
where pk.TABLE_NAME = '" + TName + @."'
and CONSTRAINT_TYPE = 'PRIMARY KEY'
and c.TABLE_NAME = pk.TABLE_NAME
and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME", conn);
DataTable PKTable = new DataTable();
PKTableAdapter.Fill(PKTable);
switch (tcontext.TriggerAction)//Switch on the Action occuring on the Table
{
case TriggerAction.Update:
iRow = inserted.Rows[0];//Get the inserted values in row form
dRow = deleted.Rows[0];//Get the overwritten values in row form
PKString = PKStringBuilder(PKTable, iRow);//the the Primary Keys and There values as a string
foreach (DataColumn column in inserted.Columns)//Walk through all possible Table Columns
{
if (!iRow[column.Ordinal].Equals(dRow[column.Ordinal]))//If value changed
{
//Build an Audit Entry
aRow = AuditTable.NewRow();
aRow["ActionType"] = "U";//U for Update
aRow["TableName"] = TName;
aRow["PK"] = PKString;
aRow["FieldName"] = column.ColumnName;
aRow["OldValue"] = dRow[column.Ordinal].ToString();
aRow["NewValue"] = iRow[column.Ordinal].ToString();
aRow["ChangeDateTime"] = DateTime.Now.ToString();
aRow["ChangedBy"] = User;
AuditTable.Rows.InsertAt(aRow, 0);//Insert the entry
}
}
break;
case TriggerAction.Insert:
iRow = inserted.Rows[0];
PKString = PKStringBuilder(PKTable, iRow);
foreach (DataColumn column in inserted.Columns)
{
//Build an Audit Entry
aRow = AuditTable.NewRow();
aRow["ActionType"] = "I";//I for Insert
aRow["TableName"] = TName;
aRow["PK"] = PKString;
aRow["FieldName"] = column.ColumnName;
aRow["OldValue"] = null;
aRow["NewValue"] = iRow[column.Ordinal].ToString();
aRow["ChangeDateTime"] = DateTime.Now.ToString();
aRow["ChangedBy"] = User;
AuditTable.Rows.InsertAt(aRow, 0);//Insert the Entry
}
break;
case TriggerAction.Delete:
dRow = deleted.Rows[0];
PKString = PKStringBuilder(PKTable, dRow);
foreach (DataColumn column in inserted.Columns)
{
//Build and Audit Entry
aRow = AuditTable.NewRow();
aRow["ActionType"] = "D";//D for Delete
aRow["TableName"] = TName;
aRow["PK"] = PKString;
aRow["FieldName"] = column.ColumnName;
aRow["OldValue"] = dRow[column.Ordinal].ToString();
aRow["NewValue"] = null;
aRow["ChangeDateTime"] = DateTime.Now.ToString();
aRow["ChangedBy"] = User;
AuditTable.Rows.InsertAt(aRow, 0);//Insert the Entry
}
break;
default:
//Do Nothing
break;
}
AuditAdapter.Update(AuditTable);//Write all Audit Entries back to AuditTable
conn.Close(); //Close the Connection
}
}

//Helper function that takes a Table of the Primary Key Column Names and the modified rows Values
//and builds a string of the form "<PKColumn1Name=Value1>,PKColumn2Name=Value2>,......"
public static string PKStringBuilder(DataTable primaryKeysTable, DataRow valuesDataRow)
{
string temp = String.Empty;
foreach (DataRow kColumn in primaryKeysTable.Rows)//for all Primary Keys of the Table that is being changed
{
temp = String.Concat(temp, String.Concat("<", kColumn[0].ToString(), "=", valuesDataRow[kColumn[0].ToString)].ToString(), ">,"));
}
return temp;
}
}

The trick was getting the Table Name and the Primary Key Columns.
I hope this code is found useful.

Comments and Suggestion will be much appreciated.

Hi Shane,

This can be used to extend the below code I found on google for Visual Basic generit audit as well...

http://sqljunkies.com/Article/4CD01686-5178-490C-A90A-5AEEF5E35915.scuk

Just adding this code to David Ziffer's work (after opening connection):

Dim SourceTableStr As String = ""

Dim cmd As New SqlCommand

With cmd

.CommandText = "SELECT object_name(resource_associated_entity_id) " & _

"FROM sys.dm_tran_locks WHERE " & _

"request_session_id = @.@.spid and " & _

"resource_type = 'OBJECT' "

.Connection = Connection

.CommandType = CommandType.Text

SourceTableStr = cmd.ExecuteScalar().ToString

End With

And somewhere down the code, add:

TableName = SourceTableStr

Then, also make sure you

GRANT VIEW SERVER STATE to [username]

on the master database on your server, since this permission is required to access sys.dm_tran_locks ( I suppose that's also required for your C# code unless you assume the user has such right e.g. sysadmin).

Generic Audit Trigger CLR C#(Works when the trigger is attached to any Table)

This Audit Trigger is Generic (i.e. non-"Table Specific") attach it to any tabel and it should work. Be sure and create the 'Audit' table first though.

The following code write audit entries to a Table called
'Audit'
with columns
'ActionType' //varchar
'TableName' //varchar
'PK' //varchar
'FieldName' //varchar
'OldValue' //varchar
'NewValue' //varchar
'ChangeDateTime' //datetime
'ChangeBy' //varchar

using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;

public partial class Triggers
{
//A Generic Trigger for Insert, Update and Delete Actions on any Table
[Microsoft.SqlServer.Server.SqlTrigger(Name = "AuditTrigger", Event = "FOR INSERT, UPDATE, DELETE")]

public static void AuditTrigger()
{
SqlTriggerContext tcontext = SqlContext.TriggerContext; //Trigger Context
string TName; //Where we store the Altered Table's Name
string User; //Where we will store the Database Username
DataRow iRow; //DataRow to hold the inserted values
DataRow dRow; //DataRow to how the deleted/overwritten values
DataRow aRow; //Audit DataRow to build our Audit entry with
string PKString; //Will temporarily store the Primary Key Column Names and Values here
using (SqlConnection conn = new SqlConnection("context connection=true"))//Our Connection
{
conn.Open();//Open the Connection
//Build the AuditAdapter and Mathcing Table
SqlDataAdapter AuditAdapter = new SqlDataAdapter("SELECT * FROM Audit WHERE 1=0", conn);
DataTable AuditTable = new DataTable();
AuditAdapter.FillSchema(AuditTable, SchemaType.Source);
SqlCommandBuilder AuditCommandBuilder = new SqlCommandBuilder(AuditAdapter);//Populates the Insert command for us
//Get the inserted values
SqlDataAdapter Loader = new SqlDataAdapter("SELECT * from INSERTED", conn);
DataTable inserted = new DataTable();
Loader.Fill(inserted);
//Get the deleted and/or overwritten values
Loader.SelectCommand.CommandText = "SELECT * from DELETED";
DataTable deleted = new DataTable();
Loader.Fill(deleted);
//Retrieve the Name of the Table that currently has a lock from the executing command(i.e. the one that caused this trigger to fire)
SqlCommand cmd = new SqlCommand("SELECT object_name(resource_associated_entity_id) FROM
ys.dm_tran_locks WHERE request_session_id = @.@.spid and resource_type = 'OBJECT'", conn);
TName = cmd.ExecuteScalar().ToString();
//Retrieve the UserName of the current Database User
SqlCommand curUserCommand = new SqlCommand("SELECT system_user", conn);
User = curUserCommand.ExecuteScalar().ToString();
//Adapted the following command from a T-SQL audit trigger by Nigel Rivett
//http://www.nigelrivett.net/AuditTrailTrigger.html
SqlDataAdapter PKTableAdapter = new SqlDataAdapter(@."SELECT c.COLUMN_NAME
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
where pk.TABLE_NAME = '" + TName + @."'
and CONSTRAINT_TYPE = 'PRIMARY KEY'
and c.TABLE_NAME = pk.TABLE_NAME
and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME", conn);
DataTable PKTable = new DataTable();
PKTableAdapter.Fill(PKTable);
switch (tcontext.TriggerAction)//Switch on the Action occuring on the Table
{
case TriggerAction.Update:
iRow = inserted.Rows[0];//Get the inserted values in row form
dRow = deleted.Rows[0];//Get the overwritten values in row form
PKString = PKStringBuilder(PKTable, iRow);//the the Primary Keys and There values as a string
foreach (DataColumn column in inserted.Columns)//Walk through all possible Table Columns
{
if (!iRow[column.Ordinal].Equals(dRow[column.Ordinal]))//If value changed
{
//Build an Audit Entry
aRow = AuditTable.NewRow();
aRow["ActionType"] = "U";//U for Update
aRow["TableName"] = TName;
aRow["PK"] = PKString;
aRow["FieldName"] = column.ColumnName;
aRow["OldValue"] = dRow[column.Ordinal].ToString();
aRow["NewValue"] = iRow[column.Ordinal].ToString();
aRow["ChangeDateTime"] = DateTime.Now.ToString();
aRow["ChangedBy"] = User;
AuditTable.Rows.InsertAt(aRow, 0);//Insert the entry
}
}
break;
case TriggerAction.Insert:
iRow = inserted.Rows[0];
PKString = PKStringBuilder(PKTable, iRow);
foreach (DataColumn column in inserted.Columns)
{
//Build an Audit Entry
aRow = AuditTable.NewRow();
aRow["ActionType"] = "I";//I for Insert
aRow["TableName"] = TName;
aRow["PK"] = PKString;
aRow["FieldName"] = column.ColumnName;
aRow["OldValue"] = null;
aRow["NewValue"] = iRow[column.Ordinal].ToString();
aRow["ChangeDateTime"] = DateTime.Now.ToString();
aRow["ChangedBy"] = User;
AuditTable.Rows.InsertAt(aRow, 0);//Insert the Entry
}
break;
case TriggerAction.Delete:
dRow = deleted.Rows[0];
PKString = PKStringBuilder(PKTable, dRow);
foreach (DataColumn column in inserted.Columns)
{
//Build and Audit Entry
aRow = AuditTable.NewRow();
aRow["ActionType"] = "D";//D for Delete
aRow["TableName"] = TName;
aRow["PK"] = PKString;
aRow["FieldName"] = column.ColumnName;
aRow["OldValue"] = dRow[column.Ordinal].ToString();
aRow["NewValue"] = null;
aRow["ChangeDateTime"] = DateTime.Now.ToString();
aRow["ChangedBy"] = User;
AuditTable.Rows.InsertAt(aRow, 0);//Insert the Entry
}
break;
default:
//Do Nothing
break;
}
AuditAdapter.Update(AuditTable);//Write all Audit Entries back to AuditTable
conn.Close(); //Close the Connection
}
}

//Helper function that takes a Table of the Primary Key Column Names and the modified rows Values
//and builds a string of the form "<PKColumn1Name=Value1>,PKColumn2Name=Value2>,......"
public static string PKStringBuilder(DataTable primaryKeysTable, DataRow valuesDataRow)
{
string temp = String.Empty;
foreach (DataRow kColumn in primaryKeysTable.Rows)//for all Primary Keys of the Table that is being changed
{
temp = String.Concat(temp, String.Concat("<", kColumn[0].ToString(), "=", valuesDataRow[kColumn[0].ToString)].ToString(), ">,"));
}
return temp;
}
}

The trick was getting the Table Name and the Primary Key Columns.
I hope this code is found useful.

Comments and Suggestion will be much appreciated.

Hi Shane,

This can be used to extend the below code I found on google for Visual Basic generit audit as well...

http://sqljunkies.com/Article/4CD01686-5178-490C-A90A-5AEEF5E35915.scuk

Just adding this code to David Ziffer's work (after opening connection):

Dim SourceTableStr As String = ""

Dim cmd As New SqlCommand

With cmd

.CommandText = "SELECT object_name(resource_associated_entity_id) " & _

"FROM sys.dm_tran_locks WHERE " & _

"request_session_id = @.@.spid and " & _

"resource_type = 'OBJECT' "

.Connection = Connection

.CommandType = CommandType.Text

SourceTableStr = cmd.ExecuteScalar().ToString

End With

And somewhere down the code, add:

TableName = SourceTableStr

Then, also make sure you

GRANT VIEW SERVER STATE to [username]

on the master database on your server, since this permission is required to access sys.dm_tran_locks ( I suppose that's also required for your C# code unless you assume the user has such right e.g. sysadmin).

sql

generic audit trail best practice questions

hello everyone,
so i have the need for an audit trail for certain activities. ideally i
would like a generic audit trail table, that denotes the kind of
activity, the entity reference it was performed on, and the entity
reference it was performed by.
for example, there are currently entities like so (greatly simplified):
create table user (
userid uniqueidentifier primary key not null,
name varchar(50),
email varchar(50))
create table employee (
employeeid uniqueidentifier primary key not null,
name varchar(50),
role varchar(50))
create table businessthingy (
thingyid uniqueidentifier primary key not null,
description varchar(50),
modifiedby uniqueidentifier not null)
create table otherbusinessthingy (
otherthingyid uniqueidentifier primary key not null,
quantity int)
create table audit (
auditid uniqueidentifier primary key not null,
activity varchar(50),
performedby uniqueidentifier not null,
performedon uniqueidentifier not null)
so that's a rough sketch. some activities are frequent enough, and
unimportant enough that just capturing rowlevel audits is fine, which
is why there is a 'modifiedby' in the businessthingy table. we don't
want or need to keep track of a full history of edits, just knowing the
last person to edit something is good enough.
the audit table will capture more important (and less frequent)
activities though, such as inserts and deletes.
so my first question is: is it even possible to impose referential
integrity in this scheme? for example, the businessthingy can be
modified by both users and employees. also, the audit event can be
performed by both users and employees, and they can be performed on
both businesthingies and otherbusinessthingies. i can freely insert the
id's in appropriately, but can i enforce referential integrity across
multiple possible tables?
my second question is: are there better practices for this kind of
thing in order to ensure referential integrity? or is this a typical
hurdle for generic activity auditing?
thanks for any help / advice,
jasonjason (iaesun@.yahoo.com) writes:
> so my first question is: is it even possible to impose referential
> integrity in this scheme? for example, the businessthingy can be
> modified by both users and employees. also, the audit event can be
> performed by both users and employees, and they can be performed on
> both businesthingies and otherbusinessthingies. i can freely insert the
> id's in appropriately, but can i enforce referential integrity across
> multiple possible tables?
I don't think you should have referential integrity for users. Those should
be readable ids or names. Think sa and Query Analyzer. That's also stuff
you need to audit.
References for the audited entities is another matter. Here it's more
relevant to referential integrity. I think we do this in most of our
auditing. Important thing if you don't you need to store something that
users can read later. Storing a GUID which means nothing is not a good
thing.
Erland Sommarskog, SQL Server MVP, esquel@.sommarskog.se
Books Online for SQL Server 2005 at
http://www.microsoft.com/technet/pr...oads/books.mspx
Books Online for SQL Server 2000 at
http://www.microsoft.com/sql/prodin...ions/books.mspx|||I've used trigger / audit table based auditing in the past, but have found
it to be cumbersome to program and brittle, especially in systems that are
complex or in a constant state of development. There were occasions where
trigger caused transactions to fail, becuase of flaws in the audit
programming or when the data model changed.
Instead, consider using SQL Server Profiler for auditing:
http://msdn.microsoft.com/library/d...
ethowto15.asp
Unlike triggers, it requires no programming and, it can be configured to
audit most any event at a ganular level: including table updates, table
selects, execs; even deadlocks, locking. The output can be directed to a SQL
Server table, and a scheduled job can query for specific red flag events and
send notifications. You can get it up and running in a single afternoon, and
it's simple to enable / disable events on specific objects or users, if
needed. Also, it can be used for analyzing system performance.
"jason" <iaesun@.yahoo.com> wrote in message
news:1137171394.413876.147000@.z14g2000cwz.googlegroups.com...
> hello everyone,
> so i have the need for an audit trail for certain activities. ideally i
> would like a generic audit trail table, that denotes the kind of
> activity, the entity reference it was performed on, and the entity
> reference it was performed by.
> for example, there are currently entities like so (greatly simplified):
> create table user (
> userid uniqueidentifier primary key not null,
> name varchar(50),
> email varchar(50))
> create table employee (
> employeeid uniqueidentifier primary key not null,
> name varchar(50),
> role varchar(50))
> create table businessthingy (
> thingyid uniqueidentifier primary key not null,
> description varchar(50),
> modifiedby uniqueidentifier not null)
> create table otherbusinessthingy (
> otherthingyid uniqueidentifier primary key not null,
> quantity int)
> create table audit (
> auditid uniqueidentifier primary key not null,
> activity varchar(50),
> performedby uniqueidentifier not null,
> performedon uniqueidentifier not null)
> so that's a rough sketch. some activities are frequent enough, and
> unimportant enough that just capturing rowlevel audits is fine, which
> is why there is a 'modifiedby' in the businessthingy table. we don't
> want or need to keep track of a full history of edits, just knowing the
> last person to edit something is good enough.
> the audit table will capture more important (and less frequent)
> activities though, such as inserts and deletes.
> so my first question is: is it even possible to impose referential
> integrity in this scheme? for example, the businessthingy can be
> modified by both users and employees. also, the audit event can be
> performed by both users and employees, and they can be performed on
> both businesthingies and otherbusinessthingies. i can freely insert the
> id's in appropriately, but can i enforce referential integrity across
> multiple possible tables?
> my second question is: are there better practices for this kind of
> thing in order to ensure referential integrity? or is this a typical
> hurdle for generic activity auditing?
> thanks for any help / advice,
> jason
>|||we have deliberately chosen guid's for all of our entity relational
keys for a variety of reasons, so wherever we are referring to another
singular entity, guid is how we want to do it. if we want to pull user
digestible information, then we do that as reporting functionality
through the foreign relationship of entities.
so i'm curious, how do you enforce referential integrity for diverse
audited entities?|||interesting. i'm not sure this would have the 'user' level that we
would need though. you see users of the application do not map to
database users. we use the application profile for sql identification,
and rely on data content to define the state of user entities (ergo the
user table). could we, in that setup, use profiler to capture data at a
user level, when the user entities are not database users, but
application users stored in a table?|||When the application performs an operation on the database, do you call a
stored procedure with the user's id as a parameter like so?
exec usp_EditInvoice @.InvoiceID=3607, @.UserID=8
"jason" <iaesun@.yahoo.com> wrote in message
news:1137182806.078657.32390@.f14g2000cwb.googlegroups.com...
> interesting. i'm not sure this would have the 'user' level that we
> would need though. you see users of the application do not map to
> database users. we use the application profile for sql identification,
> and rely on data content to define the state of user entities (ergo the
> user table). could we, in that setup, use profiler to capture data at a
> user level, when the user entities are not database users, but
> application users stored in a table?
>|||jason (iaesun@.yahoo.com) writes:
> we have deliberately chosen guid's for all of our entity relational
> keys for a variety of reasons, so wherever we are referring to another
> singular entity, guid is how we want to do it. if we want to pull user
> digestible information, then we do that as reporting functionality
> through the foreign relationship of entities.
OK. But keep in mind what said about the user names.

> so i'm curious, how do you enforce referential integrity for diverse
> audited entities?
So, I lied a bit. :-)
Some of our autiding stuff are specific to a process, so there are no
generic columns, but all columns have a unique meaning. Ref. integrity is
trivial.
Then we have at least one generic log table where the keys are
"tablename", "keyvalue1", "keyvalue2", and "columnname". This table
has no referential integrity.
For SQL 2005 our idea is to improve our auditing (which is a bit
substandard), by using the xml datatype, and simply log an after-
image of the row with XML. A generic client could then easily show
the user only differences between two updates.
Erland Sommarskog, SQL Server MVP, esquel@.sommarskog.se
Books Online for SQL Server 2005 at
http://www.microsoft.com/technet/pr...oads/books.mspx
Books Online for SQL Server 2000 at
http://www.microsoft.com/sql/prodin...ions/books.mspx|||yes, something quite like that. we call it an 'authorityid' but
basically equates to either a userid or an employeeid, depending on who
is interacting with the entity|||interesting. is there any concern whether the XML after-image affects
the throughput of each transaction significantly?
thanks very much for this information. your practices make a lot of
sense, in terms of which audits can and cannot have referential
integrity. very helpful!
jason|||SQL Server Profiler can trace the command text of stored procedure calls,
including what parameters were passed. This will tell you what was executed,
when, and by whom. It can also trace other event attributes such as the
duration of the execution and application name. For example:
2005/01/16 10:31am exec usp_EditInvoice @.InvoiceID=3607, @.AuthroityID=8
However, using seperate SQL Server logins for each user (especially Windows
Authenitcated accounts) makes for easier and more robust auditing.
"jason" <iaesun@.yahoo.com> wrote in message
news:1137421941.558008.58370@.z14g2000cwz.googlegroups.com...
> yes, something quite like that. we call it an 'authorityid' but
> basically equates to either a userid or an employeeid, depending on who
> is interacting with the entity
>

Monday, March 19, 2012

Generating an audit trail in SQLS 2000

Hi,
Following on from a recent post regarding Identity fields with David Portas,
I was discussing a few issues with a collegue and an interesting issue was
raised.
Namely, creating an audit trail in which every row is assigned a unique,
gapless sequential reference. For example, our accountancy package (Sage)
has an audit trail in which every transaction has a numbered reference. Each
number is unique and there are no missing numbers. Obviously some
transactions are cancelled, others may not be assigned in exactly the order
that they were created, etc etc.
I'm just wondering how I would achieve such a thing in SQLS. From talking
with David Portas in my last thread I understand that IDENTITY is not the
way ot achieve this, but I'm curious as to how exactly to achieve this in a
multi-user concurrent environment?
For example, have a unique ID table in which numbers are assigned as they
are required - although I imagine that this would require extensive use of
transactions and locking...
Alternatively would be to have an ON INSERT trigger which simply assigns the
last row reference plus one to the reference field?
Any pointers / sites / etc?
I appreciate that this is a big vague but it's something that I can see
being very useful in the near future.
Any and all advice is gratefully received.
Regards
Chris.One possible solution is at
http://solidqualitylearning.com/blo...04/04/446.aspx.
Dejan Sarka, SQL Server MVP
Associate Mentor
www.SolidQualityLearning.com
"Chris Strug" <hotmail@.solace1884.com> wrote in message
news:%23r5dC30PFHA.1528@.TK2MSFTNGP09.phx.gbl...
> Hi,
> Following on from a recent post regarding Identity fields with David
Portas,
> I was discussing a few issues with a collegue and an interesting issue was
> raised.
> Namely, creating an audit trail in which every row is assigned a unique,
> gapless sequential reference. For example, our accountancy package (Sage)
> has an audit trail in which every transaction has a numbered reference.
Each
> number is unique and there are no missing numbers. Obviously some
> transactions are cancelled, others may not be assigned in exactly the
order
> that they were created, etc etc.
> I'm just wondering how I would achieve such a thing in SQLS. From talking
> with David Portas in my last thread I understand that IDENTITY is not the
> way ot achieve this, but I'm curious as to how exactly to achieve this in
a
> multi-user concurrent environment?
> For example, have a unique ID table in which numbers are assigned as they
> are required - although I imagine that this would require extensive use of
> transactions and locking...
> Alternatively would be to have an ON INSERT trigger which simply assigns
the
> last row reference plus one to the reference field?
> Any pointers / sites / etc?
> I appreciate that this is a big vague but it's something that I can see
> being very useful in the near future.
> Any and all advice is gratefully received.
> Regards
> Chris.
>
>|||If you don't mind having gaps in numbers i.e reserve IDs before you
write. Sticking the next available ID in a one row table is good
performance wise. You only lock one very small table rather than the
table you are inserting data into.
The processes that will write can take their time because they have
their unique IDs already and so don't stop other writes reserving
their IDs. Also you can reserve 100 IDs very easily at no cost just by
adding 100.
But you will get gaps, after reserving ID if your write fails.

Sunday, February 19, 2012

General: Audit Dimension

What is the best practice for setting up audit dimensions in 2005? I know that we have the audit transform available, but should we be capturing extra information beyond this?

I had seen one of the design tip articles by kimball on what should be added, however, this article was from some time ago... (http://kimballgroup.com/html/designtipsPDF/DesignTips2001/KimballDT26AddingAnAudit.pdf)

Also, I try to capture the audit guid in the record and then, later, capture the rest of the audit information... I notice that the guid's are different between the audit transforms... is there a way to capture all of the information but not write it all out to each record? i.e. only capture the guid to the record and the rest of the information to the audit table.

Take a look at the Kimball Webcast on the first page of this forum:. Joy talks about creating auditing features:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=534505&SiteID=1|||

Thanks phil. I have watched that video in the past, I might take another look at it. I was, however, hoping for a bit of discussion on what others in the industry were doing. I have looked through the Microsoft Data Warehouse Toolkit, Practical B.I. for SQL Server 2005, Delivering B.I. with Microsoft 2005, as well as a few of the articles on the web. I was just curious, since each has a different level of detail which they capture, what is acceptable down the road (I was told at one of the tdwi conferences that when building the warehouse, the first thing most people would do if they had the oppurtunity to start over is set up a good mdm / audit system, so that is what I'm trying to do)...

|||I implement a version of what Joy illustrated in her Webcast.