SQLServerCentral Article

Imaginative Auditing with Rollback (Undo) and RollForward (Redo) Part II

,

If you haven’t read Part I, now might be a good time to do so and get some background on this series.

My specialist area seems to be XML and XSL with SQL Server.  It is in fact a very specialist area – as far as I can gather there are only a handful of us.  I’m sure by now most of you have a fair idea about what XML is, and know that SQL Server can return data in XML format.  For those who don’t know what XSL is, though, it’s a stylesheet language used to transform XML into other formats.

The goal of this article is to generate audit triggers for all tables in a database in a single operation.  What's more, the audit table will record SQL to rollback and rollforward changes. To do this, I’ll be using XML and XSL, and here’s how.  The audit trigger needs to know about the fields in the table being audited - the datatypes, and which fields form the primary key etc..  This type of information is contained in various system tables / views.  The sql below queries the system tables to return XML which describes all tables in the database against which it is executed. 

WITH    pkcolumns
          AS ( SELECT   si.name,
                        sic.column_id,
                        si.object_id
               FROM     sys.indexes si
                        INNER JOIN sys.index_columns sic ON si.index_id = sic.index_id
                                                            AND si.object_id = sic.object_id
               WHERE    si.is_primary_key = 1
             ) ,
        tables
          AS ( SELECT   t.name AS tableName,
                        t.object_id AS objectId,
                        s.name AS schemaName
               FROM     sys.tables t
                        INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
               WHERE    t.type = 'U'
             ) ,
        columns
          AS ( SELECT   OBJECT_NAME(col.object_id) AS objectName,
                        col.object_id AS objectId,
                        col.column_id AS columnId,
                        col.name AS columnName,
                        col.is_identity AS isIdentity,
                        pk.name AS pkName,
                        typ.name AS typeName,
                        CASE WHEN typ.name LIKE '%datetime%' THEN 1
                             WHEN typ.precision = 0 THEN 1
                             ELSE 0
                        END AS 'takesQuotes',
                        CASE WHEN typ.NAME LIKE '%datetime%' THEN 1
                             ELSE 0
                        END AS isDateTime,
                        col.is_nullable AS isNullable
               FROM     sys.columns col
                        LEFT JOIN pkcolumns pk ON col.object_id = pk.object_id
                                                  AND col.column_id = pk.column_id
                        INNER JOIN sys.types typ ON col.user_type_id = typ.user_type_id
                                                    AND col.system_type_id = typ.system_type_id
--order by object_name(col.object_id), column_id
             )
    SELECT  [name],
            compatibility_level,
            ( SELECT    t.tableName,
                        t.schemaName,
                        t.objectId,
                        ( SELECT    c.isIdentity "@isIdentity",
                                    c.isDateTime "@isDateTime",
                                    c.takesQuotes "@takesQuotes",
                                    c.isNullable "@isNullable",
                                    c.columnName,
                                    c.pkName,
                                    c.typeName
                          FROM      columns c
                          WHERE     c.objectId = t.objectId
                          ORDER BY  objectName,
                                    columnId
                        FOR
                          XML PATH('column'),
                              TYPE
                        )
              FROM      tables t
            FOR
              XML PATH('table'),
                  TYPE
            )
    FROM    sys.databases AS [database]
    WHERE   DB_NAME() = [database].name
FOR     XML AUTO

If you look closely, you'll see I've used Common Table Expressions to simplify the XML generation. Each CTE represents a different level in the XML hierarchy.

The structure of the XML document thus created is shown by the schema diagram below (generated with XML Spy.)

XML Schema

Figure 1 Schema describing the XML

The next stage is to write the XSL document which will transform this XML into the audit triggers, as seen in Part I.  I’m not going to go into to any detail about how the XSL sheet is structured, except to say that I found writing it very challenging, as the XSL document was a strange mixture of XML, SQL of the trigger and SQL generated by the trigger.  The final result isn’t the most beautiful XSL sheet I’ve ever written, but it does the job it’s supposed to do.

A variety of methods may be used to carry out the actual XSL transformation.

I outlined one such method using SSIS in a previous article I've got the XML - Now What? .  You can also use a tool such as XML Spy or a CLR function. There is even a command line utility available as a free download from Microsoft.

The result of the transformation is a SQL script to generate the audit triggers.  All being well, you should simply be able to run it against your database.  It will try to audit to a table called BigAudit in a database called Audit.  The scripts to generate the audit database and audit table are included in Part I.

I hope you’ve managed to keep up, and are now happily playing with your new auditing solution.  Of course, there are various reasons why in practice you probably aren’t going to want to audit all tables in your database (or on your SQL Server instance) to a single audit table.  (I wrote this principally to see if it could be done, rather than to meet a requirement.)  However I do believe there are many potential practical applications for such a solution, and I look forward to hearing your views on this.

Resources

Rate

4.75 (16)

You rated this post out of 5. Change rating

Share

Share

Rate

4.75 (16)

You rated this post out of 5. Change rating