views:

1105

answers:

7

I am looking for a good way to log changes that occur on a particular set of tables in my SQL Server 2005 database. I believe the best way to do this is through a trigger that get's executed on updates and deletes. Is there anyway to grab the actual statement that is being ran? Once I have the statement I can easily log it someplace else (other DB table). However, I have not found an easy way (if possible) to grab the SQL statement that is being ran.

+4  A: 

You should be able to accomplish this using the system management views.

An example would be something like this:

SELECT er.session_id,
  er.status,
  er.command,
  DB_NAME(database_id) AS 'DatabaseName',
  user_id,
  st.text
FROM sys.dm_exec_requests AS er
  CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS st
WHERE er.session_id = @@SPID;

I'm not sure this will be as useful to you as a more data-centric logging mechanism might be, though.

mwigdahl
+1 Interesting, though doing this in a trigger feels kinda wrong
Andomar
This is cool, i hadn't realised these were available. However, they don't show the statement that's doing the updating - it looks like they show the entire batch. Still very useful and perhaps will fit requirements.
Rory
Unfortunately, we do not have a stored procedure that is being ran to update the tables. This is an old system that is having problems and we are trying to diagnose who is running updates/deletes and what statements they are running.
Jason
Yes, you would put this code in the trigger for the tables in question and log the results to another table for later analysis. That will catch any updates from anywhere.
mwigdahl
+1  A: 

do you really need to log the statement that ran, most people log the changed data (INSERTED and DELETED tables within the trigger).

KM
+2  A: 

MSSQL has virtual tables named 'Inserted' and 'Deleted', which contain newly-inserted and/or newly-deleted and/or newly-updated data records, which you can access from a trigger ... I use these, to know what data has changed (that's instead of being told what statement changed the data).

ChrisW
+2  A: 

Don't forget that your logging will be part of the transaction so if there is an error and you rollback the transaction, your log will also be deleted.

idstam
+1 Someone at work got into a big quarrel with a customer. He said "no logging, no problem". Turned out he was logging from a transaction...
Andomar
Yepp, I've been that dude :)
idstam
+2  A: 

Triggers are bad, I'd stay away from triggers.

If you are trying to troubleshoot something, attach Sql Profiler to the database with specific conditions. This will log every query run for your inspection.

Another option is to change to calling program to log its queries. This is a very common practice.

Andomar
Unfortuneatly, our DBA's don't want to turn Profiler on as they told me even if you use specific conditions it still has to go through every query that comes to the database. Also, I can't do it from the applications at this time as they are legacy and in many different places. Is there no way to get the actual statement from within a trigger?
Jason
Side stepping your actual question, but try to improve your relation with the DBA's? You can demonstrate on a test server that Profiler does not have a big impact. Invite the DBA to a talk with business stakeholder; that gives them an idea it's serious, and at the end of the day, the business pays the DBA's salary.
Andomar
Finally convinced the DBA's to turn on SQL Profiler. However, it only get's turned on for a week.
Jason
Why are triggers bad?
Josef Sábl
Triggers add complexity that is hard to see; you don't expect a trigger exception when you update/insert a table. Triggers are hard to debug: changing them can break an entirely unrelated piece of software, often unexpectedly. Triggers are tricky to write, especially for high load sytems. And I'll just mention triggers that trigger other triggers and let you figure out whether that's a good idea :)
Andomar
A: 

Triggers are a good way to ensure that any changes are logged, since they will almost always fire regardless of how the updates are performed - e.g. ad-hoc connections as well as application connections.

As suggested by @mwigdahl, the system management views look like a good way to capture the current running batch. Whether that's particularly useful to log in the trigger is another thing.

One downside to using triggers is that you can only identify the source of the update from the database connection. Many applications don't have any user information associated with the connection, to facilitate connection pooling, so you don't know which user is performing the action. ie the Login used by the connection is a generic application login rather than the person using the application. The normal way to get around this is to use stored procedures as the interface to all database interaction, and then ensure that a UserId is passed with all procedure calls. You can then perform your logging via the stored procedure instead of a trigger. Clearly this is only useful if you know people won't update tables directly without using the procedures, or don't need to log that situation.

The ability to get the currently executing batch might provide an even better mechanism: if you ensure that all your sql batches contain a UserId you could then extract this from the sql within your trigger. That would allow you to do all logging with triggers, which means you capture everything, but also let you associate changes with a particular user.

If you're going down the trigger route it's worth checking the situations triggers aren't fired (maybe bulk loaded data? or if people have permission to disable triggers).

Also consider as @idstam pointed out that trigger code will be within your transaction so will normally be logged and rolled back along with it.

Another thing to consider when writing triggers is the behaviour of @@IDENTITY: if you have procedures using @@IDENTITY you might accidentally change their behaviour.

Rory
For those who don't know, @@identity should in general not be used in any databse with triggers. If the trigger inserts to a table with an identity, that is what is returned not the identity of the original record insert. Scope_identity() fixes this or in newer versions of SQl Server you can use the output clause instead.
HLGEM
A: 

Be careful here, since triggers fire at the ROW level, not the SQL STATEMENT level. So, if someone does "DELETE FROM BIGTABLE", your trigger will fire for each row in that table (this specifically in regard to the fact that you want to know the SQL statement that performed the operation, so you'll need to "figure that out" for every row the statement affects).

Will Hartung
This is NOT true! Triggers fire once per action whether you delete/update/insert a million rows or 1. This is why all triggers must be written to handle multiple records inserts or deletes and not through the use of a cursor either (unless you like blocking and poor performance)!
HLGEM