1

I found the following query from this question which appears to show row deletions from a specific table:

DECLARE @TableName sysname
SET @TableName = 'dbo.ObjectInstances'

SELECT u.[name] AS UserName , l.[Begin Time] AS TransactionStartTime FROM fn_dblog(NULL, NULL) l INNER JOIN ( SELECT [Transaction ID] FROM fn_dblog(NULL, NULL) WHERE AllocUnitName LIKE @TableName + '%' AND Operation = 'LOP_DELETE_ROWS' ) deletes ON deletes.[Transaction ID] = l.[Transaction ID] INNER JOIN sysusers u ON u.[sid] = l.[Transaction SID]

The results all show the same username, which is the username we use from our app to connect to the database. However, I had just deleted a row using SSMS, while logged in using Windows Authentication. This record is not shown in the results from the query above.

How can I view an audit of rows/records deleted using SSMS (right-click, delete)?

THE JOATMON
  • 339
  • 2
  • 4
  • 13
  • 1
    Did you delete a row using a DELETE command in a query window (and are you sure you didn't start a transaction and it's still open), or did you use the Edit N Rows UI (in which case perhaps the changes haven't yet committed)? Are you sure the window you're using is using Windows auth? What does SELECT @@TRANCOUNT, SUSER_SNAME(); say? – Aaron Bertrand Aug 31 '20 at 18:20
  • Yes, I used "Edit Top 200 Rows" and then right-click > delete. I'm sure I'm using windows auth... your query shows my machinename\accountname. – THE JOATMON Aug 31 '20 at 20:37
  • Sorry, the post didn't clarify Edit Top 200 Rows, so I wanted to be sure I understand what you meant (and there are two different ways to right-click, delete just in that one dialog). But also see this - Edit Top 200 Rows is a pretty reckless way to modify data IMHO. – Aaron Bertrand Aug 31 '20 at 20:41

1 Answers1

2

In general you should use sys.database_principals instead of sysusers, which was deprecated in 2005 (just look at the big warning in the docs). But in this case, you can do this without joining to anything - SUSER_SNAME() against the sid retrieved the Windows username for me both writing a proper DELETE query and also when using the GUI as a crutch:

DECLARE @TableName sysname = N'dbo.ObjectInstances';

SELECT UserName = SUSER_SNAME(l.[Transaction SID]), l.[Begin Time] FROM sys.fn_dblog(NULL, NULL) AS l INNER JOIN ( SELECT [Transaction ID] FROM sys.fn_dblog(NULL, NULL) WHERE AllocUnitName LIKE @TableName + N'%' AND Operation = N'LOP_DELETE_ROWS' ) AS deletes ON deletes.[Transaction ID] = l.[Transaction ID] AND l.Operation = N'LOP_BEGIN_XACT';

You may also want to consider joining a 3rd time to be sure the sequence was finalized with a LOP_COMMIT_XACT (in other words, maybe you don't need to audit transactions that are still active or were rolled back, and this would also give you a better idea of when the change was actually committed vs. when they started thinking about making a change).

And just as an aside:

Aaron Bertrand
  • 180,303
  • 28
  • 400
  • 614
  • This did produce additional rows that look more like I expected, unfortunately the UserName is NULL. Ideas? – THE JOATMON Sep 01 '20 at 13:28
  • @Devil'sAdvocate Is it possible you are gaining access via a group and not your Windows username? – Aaron Bertrand Sep 01 '20 at 13:30
  • Damn. Entirely possible since I just went through the process of setting that up a few days ago (which was a pain). But I didn't want to have to add all the developers individually. So that screwed my auditing? – THE JOATMON Sep 01 '20 at 13:31
  • I'm sure there is a solution, but you may have to hunt and peck for it. Take the [Transaction SID] from the LOP_BEGIN_XACT row and look for that sid in sys.server_principals and sys.database_principals. I will try to repro but my dev environment is local Docker containers on a Mac so it isn't very easy to simulate your scenario. Keep in mind that if you allow a developer in via a group, in a lot of scenarios you effectively audit the group, not each individual member. – Aaron Bertrand Sep 01 '20 at 13:47
  • I appreciate the help and advice. Since your solution did work for my test table as expected, I have marked it as accepted. – THE JOATMON Sep 01 '20 at 14:01
  • Side question: What does the N do in your declare statement? – THE JOATMON Sep 01 '20 at 14:11
  • 1
    There may be exceptions, but almost all metadata in SQL Server is Unicode. The N prefix makes sure that if you have Unicode characters, they're not lost in translation, so it's just a good habit to always use it when the type is nchar/nvarchar. To see what happens when you leave it out, compare: SELECT WithNPrefix = N'我', WithoutNPrefix = '我'; but also see this post for potential performance impact if you apply the N prefix when comparing to a char/varchar column. – Aaron Bertrand Sep 01 '20 at 14:27