SQL Server 2022 improved the STRING_SPLIT function so that it can now return lists that are guaranteed to be in order. However, that’s the only thing they improved – there’s still a critical performance problem with it.
Let’s take the Stack Overflow database, Users table, put in an index on Location, and then test a couple of queries that use STRING_SPLIT to parse a parameter that’s an incoming list of locations:
CREATE INDEX Location ON dbo.Users(Location);SET STATISTICS IO ON;GOCREATE OR ALTER PROC dbo.usp\_GetUsersByLocation\_Subquery@LocationList NVARCHAR(4000) ASSELECT TOP 1000 u.*FROM dbo.Users uWHERE u.Location IN (SELECT value FROM STRING\_SPLIT(@LocationList, N',', 1))ORDER BY u.Reputation DESC;GOCREATE OR ALTER PROC dbo.usp\_GetUsersByLocation\_Join@LocationList NVARCHAR(4000) ASSELECT TOP 1000 u.*FROM STRING\_SPLIT(@LocationList, N',', 1) lINNER JOIN dbo.Users u ON l.value = u.LocationORDER BY u.Reputation DESC;GOEXEC usp\_GetUsersByLocation\_Subquery N'India,China';EXEC usp\_GetUsersByLocation\_Join N'India,China';
The two queries produce slightly different actual execution plans, but the way STRING_SPLIT behaves is the same in both, so I’m just going to take the first query to use as an illustration:
That red-highlighted part has two problems:
As a result, everything else in the query plan is doomed. The estimates are all garbage. SQL Server will choose the wrong indexes, process the wrong tables first, make the wrong parallelism decisions, be completely wrong about memory grants, you name it.
Like I wrote in this week’s post about DATETRUNC, that doesn’t make STRING_SPLIT a bad tool. It’s a perfectly fine tool if you need to parse a string into a list of values – but don’t use it in a WHERE clause, so to speak. Don’t rely on it to perform well as part of a larger query that involves joins to other tables.
Working around STRING_SPLIT’s problemsOne potential fix is to dump the contents of the string into a temp table first:
CREATE OR ALTER PROC dbo.usp\_GetUsersByLocation\_TempTable@LocationList NVARCHAR(4000) ASBEGINSELECT valueINTO #LocationListFROM STRING\_SPLIT(@LocationList, N',', 1);SELECT TOP 1000 u.*FROM dbo.Users uWHERE u.Location IN (SELECT value FROM #LocationList)ORDER BY u.Reputation DESC;ENDGOEXEC usp\_GetUsersByLocation\_TempTable N'India,China';
And the actual execution plan is way better than the prior examples. You can see the full plan by clicking that link, but I’m just going to focus on the relevant STRING_SPLIT section and the index seek:
This plan is better because:
Woohoo! Just remember that temp tables are like OPTION (RANDOM RECOMPILE), like I teach you in this Fundamentals of TempDB lecture.