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