Log and Filter sp_who2 Results in SQL Server

sp_who2 is the first thing most DBAs run when a server feels busy, and the second thing most DBAs want is to filter it. You cannot. It is a stored procedure, so there is no WHERE clause to add and no column to order by. The output arrives, you read it, and that is that.

The way round it is to capture the result set into a table first. Everything below was run on a SQL Server 2025 instance, including the two errors you are most likely to hit on the way.


Capture It Into a Temp Table

Declare a table with the same thirteen columns sp_who2 returns, in the same order, then use INSERT ... EXEC:

CREATE TABLE #who2 (
    SPID        int,
    Status      varchar(1000),
    Login       sysname,
    HostName    sysname,
    BlkBy       varchar(20),
    DBName      sysname NULL,
    Command     varchar(1000),
    CPUTime     int,
    DiskIO      int,
    LastBatch   varchar(1000),
    ProgramName varchar(1000),
    SPID2       int,
    RequestID   int
);

INSERT INTO #who2 EXEC sp_who2;
sp_who2 results captured into a temp table in SSMS, with background tasks showing a NULL DBName
The captured output, now an ordinary result set you can filter, sort and join. The highlighted rows are background tasks returning a NULL DBName, which is why the table definition has to leave that column nullable.

Now it behaves like data. Busiest sessions first:

SELECT SPID, Status, Login, HostName, DBName, CPUTime, DiskIO, ProgramName
FROM   #who2
ORDER  BY CPUTime DESC;

Only what is blocking something else:

SELECT SPID, BlkBy, Status, DBName, Command, CPUTime
FROM   #who2
WHERE  BlkBy <> '  .'          -- sp_who2 pads the not-blocked value with spaces
ORDER  BY CPUTime DESC;

Or one database, ignoring the background noise:

SELECT *
FROM   #who2
WHERE  DBName = 'YourDatabase'
AND    Status <> 'BACKGROUND'
ORDER  BY DiskIO DESC;

Two Errors You Will Hit

Both of these are the reason people give up on this technique, and both are quick to fix.

ErrorCauseFix
Msg 213
Column name or number of supplied values does not match table definition
Your table has the wrong number of columns. sp_who2 returns thirteen, and SPID appears twice. Declare all thirteen, including the second SPID and RequestID.
Msg 515
Cannot insert the value NULL into column 'DBName'
System background tasks report no database, so DBName comes back NULL. Leave DBName nullable. Whether you see this at all depends on what the server is doing at the time, which is what makes it confusing.

Microsoft’s reference covers sp_who, sys.dm_exec_sessions and sys.dm_exec_requests in full.


Keeping It: a Logging Table

The same trick works against a permanent table, which is how you answer “what was running at 09:40 this morning”. Add a timestamp column and insert into it on a schedule:

CREATE TABLE dbo.Who2Log (
    CapturedAt  datetime2(0) NOT NULL DEFAULT SYSDATETIME(),
    SPID        int,
    Status      varchar(1000),
    Login       sysname,
    HostName    sysname,
    BlkBy       varchar(20),
    DBName      sysname NULL,
    Command     varchar(1000),
    CPUTime     int,
    DiskIO      int,
    LastBatch   varchar(1000),
    ProgramName varchar(1000),
    SPID2       int,
    RequestID   int
);

-- the column list is required here: the default on CapturedAt only applies
-- if the column is left out of the insert
INSERT INTO dbo.Who2Log
    (SPID, Status, Login, HostName, BlkBy, DBName, Command,
     CPUTime, DiskIO, LastBatch, ProgramName, SPID2, RequestID)
EXEC sp_who2;

Run that from an Agent job every minute or two and you have a history. Put a clustered index on CapturedAt and prune it on a schedule, or it will grow quietly forever.


The Better Tool, If You Are Building Something

If this is a one-off look, capturing sp_who2 is fine. If you are building anything that runs on a schedule, use the DMVs instead. They are queryable directly, so there is no temp table and no column-order trap, and they carry far more than sp_who2 exposes:

SELECT  s.session_id,
        s.status,
        s.login_name,
        s.host_name,
        DB_NAME(s.database_id)  AS database_name,
        s.cpu_time,
        s.reads + s.writes      AS io,
        s.program_name,
        r.blocking_session_id,
        r.wait_type,
        r.wait_time
FROM    sys.dm_exec_sessions s
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
WHERE   s.is_user_process = 1
ORDER BY s.cpu_time DESC;

That last filter matters more than it looks. On the instance used for this post sys.dm_exec_sessions returned 81 rows and only 7 were user processes, so without is_user_process = 1 most of what you are reading is the server talking to itself. sp_who2 has no equivalent filter, which is a good part of why its output is hard to read.

And if you are reaching for this mid-incident rather than building something that runs on a schedule, install sp_whoisactive. Adam Machanic’s free procedure is the one most DBAs end up putting on every instance they look after, and it answers the question this whole post is working around: it shows active sessions only, the exact SQL each one is running, and blocking as a chain rather than a SPID you have to go and chase. One install per instance, and you do not write the capture-and-filter query again.


Frequently Asked Questions

Why can I not just add a WHERE clause to sp_who2?

Because it is a stored procedure, not a view or a function. Its output is a result set on its way to the client, and there is nowhere to attach a predicate. Capturing it into a table is the only way to filter, sort or join it.

Do I have to declare all thirteen columns?

Yes, and in order. Miss one and the insert fails with Msg 213, Column name or number of supplied values does not match table definition. Note that SPID appears twice in sp_who2’s output, once at each end of the row, which is why the table below has both SPID and SPID2.

Why does my insert fail with Msg 515 on DBName?

System background tasks return NULL for DBName, so a NOT NULL column rejects them with Msg 515, Cannot insert the value NULL. Leave the column nullable. This is easy to miss because it depends on what the server happens to be doing when you run it.

Should I be using sp_who2 at all in 2026?

For a quick look, it is fine and it is on every instance. For anything you intend to filter, sort, log or alert on, the DMVs are the better tool: they are queryable directly, they carry far more detail, and they let you exclude system sessions. On the instance used for this post sys.dm_exec_sessions returned 81 rows of which only 7 were user processes.


Related


Summary

sp_who2 cannot be filtered because it is a procedure. Capture it into a thirteen column table with INSERT ... EXEC and it becomes queryable; declare all thirteen columns or you get Msg 213, and leave DBName nullable or you get Msg 515. For anything scheduled, sys.dm_exec_sessions is the better starting point, with is_user_process = 1 to cut out the noise.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *