How to See Rowcounts and Execution Time for In-Flight Queries in SQL Server
I frequently need to see rowcounts and execution time for queries while they’re running. Maybe I’m troubleshooting a slow query …
Read Moreon • 4 min read
It can be tricky to remember that SQL Server doesn’t short circuit on IF statements in stored procedures. Even when you know this, sometimes it’s hard to recognize the impacts.

The first time that dbo.ReviewFlags is executed after the database comes online, it’s with an invalid parameter, like this:
EXEC dbo.ReviewFlags @Flag = null;
GO
This is caught by the IF block, hits the RAISERROR, and goes down to the THROW block, and the output is:
Msg 50000, Level 11, State 1, Procedure ReviewFlags, Line 8 [Batch Start Line 70] @Flag must be a value between 1 and 5
But even though SQL Server didn’t execute the SELECT statement, it still compiled it. And it also cached the plan.
When we run the procedure again with a valid value for @Flag…
EXEC dbo.ReviewFlags @Flag = 1;
GO
We get a very undersized execution plan for @Flag = 1. The row estimate is super low.

We can confirm that this plan was optimized for @Flag = null by looking at the properties of the SELECT operator in the actual plan:

We compiled for @Flag = NULL, even though this statement can never execute with that value for @Flag
For testing purposes, I can execute the procedure WITH RECOMPILE to tell SQL Server to not re-use the plan, and compile this fresh for me. (I only use this for testing, and note that if you have nested procedures or dynamic SQL, it will re-use sub-plans.)
EXEC dbo.ReviewFlags @Flag = 1 WITH RECOMPILE;
GO
This time I get a better plan, optimized for @Flag = 1…

Realistically, the first thing to consider is how likely the procedure is to be run with invalid values. If this procedure is typically executed from an application with a drop-down that doesn’t contain the invalid values and RAISERROR is unlikely to catch an unloved parameter value, you probably don’t care much (unless you work for NASA).
If you do want to make sure that the SELECT statement is optimized only when it’s executed, there are a variety of options, with different pros and cons.
You could use OPTION RECOMPILE on the statement, but the trade-off is extra CPU and a more difficult time monitoring performance with the execution plan cache long term.
Personally, I would usually rather do a different solution.
One option is a dynamic SQL style solution that allows plan re-use, like this:
DROP PROC IF EXISTS dbo.ReviewFlags;
GO
CREATE PROC dbo.ReviewFlags
@Flag TINYINT
AS
SET XACT_ABORT, NOCOUNT ON;
BEGIN TRY
IF ISNULL(@Flag,0) NOT IN (1,2,3,4,5)
RAISERROR ('@Flag must be a value between 1 and 5', 11, 1);
DECLARE @dsql NVARCHAR(MAX) = N'
SELECT
fnbd.FirstNameId,
fn.FirstName,
fnbd.Gender
FROM agg.FirstNameByYearState as fnbd
JOIN ref.FirstName as fn on
fnbd.FirstNameId = fn.FirstNameId
WHERE fnbd.Flag = @Flag';
EXEC sp_executesql @stmt = @dsql, @params = N'@Flag TINYINT', @Flag=@Flag;
END TRY
BEGIN CATCH
THROW
END CATCH
GO
The parameterized dynamic SQL still allows “healthy” (hopefully) parameter sniffing on the value passed in for @Flag, but that statement will only compile the first time that sp_executesql is actually executed. Therefore it will only “sniff” allowed parameters.
Similar to this, you could use a separate stored procedure for the SELECT statement.
Another option is to direct SQL Server what value to “sniff”. A perma-sniff, if you will. We can do this with an “optimize for” hint:
DROP PROC IF EXISTS dbo.ReviewFlags;
GO
CREATE PROC dbo.ReviewFlags
@Flag TINYINT
AS
SET XACT_ABORT, NOCOUNT ON;
BEGIN TRY
IF ISNULL(@Flag,0) NOT IN (1,2,3,4,5)
RAISERROR ('@Flag must be a value between 1 and 5', 11, 1);
SELECT
fnbd.FirstNameId,
fn.FirstName,
fnbd.Gender
FROM agg.FirstNameByYearState as fnbd
JOIN ref.FirstName as fn on
fnbd.FirstNameId = fn.FirstNameId
WHERE fnbd.Flag = @Flag
OPTION (OPTIMIZE FOR (@Flag = 1))
END TRY
BEGIN CATCH
THROW
END CATCH
GO
But if you say that out loud, people are probably going to look at you funny.
Copyright (c) 2025, Catalyze SQL, LLC; all rights reserved. Opinions expressed on this site are solely those of Kendra Little of Catalyze SQL, LLC. Content policy: Short excerpts of blog posts (3 sentences) may be republished, but longer excerpts and artwork cannot be shared without explicit permission.