Avoid enclosing Indexed Columns in a Function in the WHERE clause

By Bill Graziano on 12 September 2007 | Tags: Query Tuning


When a function is wrapped around an indexed column in the WHERE clause it will usually prevent the proper usage of that index. This article gives an example and discusses what other options are available.

I still remember the first time I ever saw this truly crush a server.  I was doing performance tuning at a client site.  We still hadn't convinced their developers that the DBA's needed to review their T-SQL code before it went into production.  One morning we noticed performance problems on the server.  Disk reads would go way, way up.  SPIDs would begin to block.  Response time on their web site slowed to a crawl -- if it responded at all.

After first insisting that no new database code was rolled out they finally admitted they'd changed one line of one WHERE clause in a single stored procedure.  That change brought the database server to a stop.  They had added a YEAR function around an indexed date column so that a report would only pull back a single year.  That changed the query plan enough to start generating table scans.

A script to simulate this looks like this:

USE AdventureWorks
GO
IF  EXISTS (SELECT * FROM sys.indexes 
			WHERE object_id = OBJECT_ID(N'[Sales].[SalesOrderHeader]') 
			AND name = N'IX_SalesOrderHeader_OrderDate')
	DROP INDEX [IX_SalesOrderHeader_OrderDate] ON [Sales].[SalesOrderHeader] 
GO
CREATE INDEX IX_SalesOrderHeader_OrderDate ON Sales.SalesOrderHeader(OrderDate)
GO

SELECT	SalesOrderID,
	OrderDate
FROM	Sales.SalesOrderHeader
WHERE	YEAR(OrderDate) = 2003
AND	MONTH(OrderDate) = 7


SELECT	SalesOrderID,
	OrderDate
FROM	Sales.SalesOrderHeader
WHERE	OrderDate BETWEEN '7/1/2003' AND '7/31/2003'
GO


IF  EXISTS (SELECT * FROM sys.indexes 
			WHERE object_id = OBJECT_ID(N'[Sales].[SalesOrderHeader]') 
			AND name = N'IX_SalesOrderHeader_OrderDate')
	DROP INDEX [IX_SalesOrderHeader_OrderDate] ON [Sales].[SalesOrderHeader] 
GO

This script creates an index on OrderDate.  In my sample queries I'm only pulling back a single month.  The first query has a cost of 0.080 and does a scan of the index.  The second query has a cost of 0.004 and does a seek on the index.  Yes, the costs are very low because I don't have a large set of data.  The first query still has a cost twenty times higher than the second.

When you wrap a function around an indexed column SQL Server must compute the value of the function for each row in the table.  When you just compare the indexed column to a scalar value or the result of a function then SQL Server can use that value to seek into the index.  The only time you can use a function around an indexed column is if you use an indexed computed column that matches the function you're using.

This type of coding is mostly used for date fields when computing ranges such as querying for the previous week.  Hopefully this little tip will save you from making the same mistake those developers did. 


Related Articles

What I Wish Developers Knew About SQL Server (Presentation) (11 October 2007)

Introduction to Parameterization in SQL Server (7 August 2007)

Best Practices for Query Parameterization (24 January 2007)

Query Parameterization and Plan Cache (23 January 2007)

Analyzing and Optimizing T-SQL Query Performance on Microsoft SQL Server using SET and DBCC (PDF) (25 July 2002)

Five Ways to Rev up Your SQL Performance (27 June 2002)

Other Recent Forum Posts

AlwaysOn AG + Replication maintenance - two scenarios to get the job done (6h)

What happens in a dual LEFT OUTER join when the second join is NULL in both tables? (8h)

How to set a variable from a table with comma? (1d)

SSRS Expression IIF Zero then ... Got #Error (2d)

Understanding 2 Left Joins in same query (3d)

Use a C# SQLReader to input an SQL hierarchyid (3d)

Translate into easier query/more understandable (3d)

Aggregation view with Min and Max (3d)

- Advertisement -