Recently, I asked to troubleshoot a performance issue with a stored procedure that was being used for reporting purposes. Looking at the execution plan, I realized that while the joins and the filters were as expected, the core bottleneck were sub-queries in the CASE expression. In order to execute the query, SQL Server needs to evaluate all the input result expressions and then return the value in the output result set based on the switch (when expression).
In case one of these input result expressions refer a large table or a table that’s locked, it could compromise the performance of the entire statement – even though the conditions are such that the table is not directly accessed (which is what was happening in our case).
The script below demonstrates the behaviour with an example. In the script, the CASE expression returns the values from one of 3 tables in the AdventureWorks database – Production.Product, Person.Person and Sales.SalesOrderHeader.
USE AdventureWorks2012; GO DECLARE @caseSwitch INT = 1; SELECT CASE @caseSwitch WHEN 1 THEN (SELECT TOP 1 pp.Name FROM Production.Product AS pp ) WHEN 2 THEN (SELECT TOP 1 per.LastName + ', ' + per.FirstName FROM Person.Person AS per ) WHEN 3 THEN (SELECT TOP 1 soh.Comment FROM Sales.SalesOrderHeader AS soh ) ELSE 'Default Value' END; GO
When we execute the script with the “Show Actual Execution Plan” (Ctrl + M) turned on, we can see that all three tables were accessed.
If this behaviour presents a lot of performance issues in the system, the solution is to re-engineer the way the system is queried such that the required set of data is staged into temporary tables to avoid loading the underlying tables.
Until we meet next time,
Be courteous. Drive responsibly.