Overview
Using functions as a part of any type of programming is usually a good practice as it generally makes code more readable and allows you to use to use it over and over again. This is also true for SQL queries except for the fact that there are cases when running the same statement over and over again might not be the most efficient way to get your result.
Explanation
Let’s take a look at a high level what happens when you use a function in the SELECT list of a query. Basically that function needs to be called for every record returned by the query. If this function contains a multi table join to do some sort of lookup this process could get quite expensive. In some cases in makes sense to remove this function call and simply join the tables from the function directly to the other tables in your query. To illustrate this point let’s take a simple query that does a lookup into another table. Below is some SQL we will need to run before out test in order to create our function and add an index on the lookup column.
CREATE FUNCTION fn_getParentDate (@ParentID bigint) RETURNS datetime AS BEGIN DECLARE @DateData datetime SELECT @DateData = DateDataColumn from [dbo].[Parent] where ParentID=@ParentID RETURN @DateData END GO CREATE NONCLUSTERED INDEX idxChild_ParentID ON [dbo].[Child] ([ParentID]) -- cleanup statements --DROP INDEX Child.idxChild_ParentID --DROP FUNCTION fn_getParentDate
Now we can write a simple query that calls this function and which does a lookup in the parent table. Here is the statement.
SELECT dbo.fn_getParentDate(ParentID),ChildID FROM [dbo].[Child]
Looking at the explain plan for this query we can see it’s going to do a scan of the Child table which makes sense since there is no WHERE clause and for each row returned it uses the index on the Parent table to do a seek for the lookup.

Now let’s rewrite this query and instead of using the function call let’s just join the Parent table in our query and add the DateDataColumn to our SELECT list. Here is the statement.
SELECT P.DateDataColumn,ChildID
FROM [dbo].[Parent] P INNER JOIN
[dbo].[Child] C ON P.ParentID=C.ParentIDLooking at the explain plan for this query we can see it only has to access the Parent table once but it now has to do a scan of this table before it performs a merge join.

It’s not entirely clear from just looking at the above explain plans which statement will perform better. The index seek in the query with the function might lead you to believe that it would be faster but let’s run the statements and take a look at the SQL Profiler results below. We can see from these results that in fact the query without the function ran more than twice as fast and used considerably less resources than the one that uses the function call.
| CPU | Reads | Writes | Duration | |
|---|---|---|---|---|
| Function | 14985 | 5705126 | 0 | 25982 |
| No Function | 578 | 5933 | 0 | 11964 |
Additional Information
Ben Snaidero has been a Database Administrator for just over 10 years. Starting out working mainly with Oracle he got into SQL Server in 2005 and has worked primarily with SQL Server for the last 3 years. His main focus with both Oracle and SQL Server is in the area of performance tuning.
- MSSQLTips Awards: Achiever (75+ tips) – 2018 | Author of the Year Contender – 2016-2017


