You see this kind of question all the time in newsgroups/forums, someone wants to return all the rows if nothing is passed in or just the rows that match the variable when something is passed in. Usually someone will reply with a suggestion to do something like this
WHERE (SomeColumn=@col OR @col IS NULL)
The problem with that approach is that it doesn’t perform well, let’s take a look, first create this table
USE tempdb
GO
CREATE TABLE Test(SomeCol1 INT NOT NULL, Somecol2 INT NOT NULL)
INSERT Test
SELECT number,low FROM master..spt_values
WHERE TYPE = 'p'
CREATE INDEX ix_test ON Test(Somecol2)
GO
Here is the query that uses the method described before, I am using AND 1=1 so that this query will match the one I will show later
DECLARE @col INT
SELECT @col = 1
SELECT SomeCol2
FROM Test
WHERE 1 =1
AND (SomeCol2=@col OR @col IS NULL)
Here is the query using dynamic SQL
GO
DECLARE @col INT
SELECT @col = 1
DECLARE @SQL NVARCHAR(4000)
SET @SQL = 'SELECT SomeCol2
FROM Test
WHERE 1 =1'
IF @col IS NOT NULL
SET @SQL = @SQL + ' AND SomeCol2=@InnerParamcol '
EXEC sp_executesql @SQL,N'@InnerParamcol INT',@col
Now let’s run these queries and take a look at the reads
SET STATISTICS IO ON
GO
DECLARE @col INT
SELECT @col = 1
SELECT SomeCol2
FROM Test
WHERE 1 =1
AND (SomeCol2=@col OR @col IS NULL)
GO
DECLARE @col INT
SELECT @col = 1
DECLARE @SQL NVARCHAR(4000)
SET @SQL = 'SELECT SomeCol2
FROM Test
WHERE 1 =1'
IF @col IS NOT NULL
SET @SQL = @SQL + ' AND SomeCol2=@InnerParamcol '
EXEC sp_executesql @SQL,N'@InnerParamcol INT',@col
SET STATISTICS IO OFF
GO
_(8 row(s) affected)
Table ‘Test’. Scan count 1, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(8 row(s) affected)
Table ‘Test’. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
As you can see the dynamic SQL query only uses 2 reads where the other solution uses 6 reads.
Here is an image of the execution plan for both queries.
The execution plan show that the dynamic SQL is using a seek where the other query is using a scan
As you can see, there is a place for dynamic SQL and if you use it correctly you will also get plan reuse, take a look at the post Changing exec to sp_executesql doesn’t provide any benefit if you are not using parameters correctly to find out how to use dynamic SQL correctly
[EDIT]
Someone on twitter suggested to try this query
DECLARE @col INT
SELECT @col = 1
SELECT SomeCol2
FROM Test
WHERE 1 =1
AND SomeCol2 = isnull(@col,SomeCol2)
That query also does an index scan.
Here is the execution plan in text for both of the queries that cause the scan
|–Index Scan(OBJECT:([tempdb].[dbo].[Test].[ix_test]),
WHERE:([tempdb].[dbo].[Test].[Somecol2]=isnull([@col],[tempdb].[dbo].[Test].[Somecol2])))
|–Index Scan(OBJECT:([tempdb].[dbo].[Test].[ix_test]),
WHERE:([tempdb].[dbo].[Test].[Somecol2]=[@col] OR [@col] IS NULL))
There was also a comment about recompiles, when you use sp_executesql you should not get recompiles when changing the value that you are passing in. I ran this query
DECLARE @col INT
SELECT @col = 1
DECLARE @SQL NVARCHAR(4000)
SET @SQL = 'SELECT SomeCol2
FROM Test
WHERE 1 =1'
IF @col IS NOT NULL
SET @SQL = @SQL + ' AND SomeCol2=@InnerParamcol '
EXEC SP_EXECUTESQL @SQL,N'@InnerParamcol INT',@col
Go
DECLARE @col INT
SELECT @col = 2
DECLARE @SQL NVARCHAR(4000)
SET @SQL = 'SELECT SomeCol2
FROM Test
WHERE 1 =1'
IF @col IS NOT NULL
SET @SQL = @SQL + ' AND SomeCol2=@InnerParamcol '
EXEC SP_EXECUTESQL @SQL,N'@InnerParamcol INT',@col
Go
DECLARE @col INT
SELECT @col = 3
DECLARE @SQL NVARCHAR(4000)
SET @SQL = 'SELECT SomeCol2
FROM Test
WHERE 1 =1'
IF @col IS NOT NULL
SET @SQL = @SQL + ' AND SomeCol2=@InnerParamcol '
EXEC SP_EXECUTESQL @SQL,N'@InnerParamcol INT',@col
Go
And then I ran a trace checking for SQL:StmtRecompile
Here is the output from that trace
[/EDIT]
*** Remember, if you have a SQL related question, try our Microsoft SQL Server Programming forum or our Microsoft SQL Server Admin forum

Denis has been working with SQL Server since version 6.5. Although he worked as an ASP/JSP/ColdFusion developer before the dot com bust, he has been working exclusively as a database developer/architect since 2002. In addition to English, Denis is also fluent in Croatian and Dutch, but he can curse in many other languages and dialects (just ask the SQL optimizer) He lives in Princeton, NJ with his wife and three kids.